diff --git a/data/index.js b/data/index.js index 25ae238fc..863eb606f 100644 --- a/data/index.js +++ b/data/index.js @@ -1,24 +1,24 @@ -export { wikipedia } from 'wmf-sitematrix'; -export { default as featureIcons } from 'maki/www/maki-sprite.json'; -export { default as suggestions } from 'name-suggestion-index/name-suggestions.json'; +export { wikipedia as dataWikipedia } from 'wmf-sitematrix'; +export { default as dataFeatureIcons } from 'maki/www/maki-sprite.json'; +export { default as dataSuggestions } from 'name-suggestion-index/name-suggestions.json'; -export { default as deprecated } from './deprecated.json'; -export { default as discarded } from './discarded.json'; -export { default as imperial } from './imperial.json'; -export { default as locales } from './locales.json'; -export { default as addressFormats } from './address-formats.json'; -export { default as phoneFormats } from './phone-formats.json'; -export { default as driveLeft } from './drive-left.json'; -export { default as imagery } from './imagery.json'; -export { default as en } from '../dist/locales/en.json'; +export { default as dataDeprecated } from './deprecated.json'; +export { default as dataDiscarded } from './discarded.json'; +export { default as dataImperial } from './imperial.json'; +export { default as dataLocales } from './locales.json'; +export { default as dataAddressFormats } from './address-formats.json'; +export { default as dataPhoneFormats } from './phone-formats.json'; +export { default as dataDriveLeft } from './drive-left.json'; +export { default as dataImagery } from './imagery.json'; +export { default as dataEn } from '../dist/locales/en.json'; -import { default as presetsData } from './presets/presets.json'; +import { default as presets } from './presets/presets.json'; import { default as defaults } from './presets/defaults.json'; import { default as categories } from './presets/categories.json'; import { default as fields } from './presets/fields.json'; -export var presets = { - presets: presetsData, +export var dataPresets = { + presets: presets, defaults: defaults, categories: categories, fields: fields diff --git a/modules/actions/add_entity.js b/modules/actions/add_entity.js index 2a802693d..de2a4893d 100644 --- a/modules/actions/add_entity.js +++ b/modules/actions/add_entity.js @@ -1,4 +1,4 @@ -export function AddEntity(way) { +export function actionAddEntity(way) { return function(graph) { return graph.replace(way); }; diff --git a/modules/actions/add_member.js b/modules/actions/add_member.js index db9d58d21..0fa740858 100644 --- a/modules/actions/add_member.js +++ b/modules/actions/add_member.js @@ -1,6 +1,7 @@ -import { joinWays } from '../geo/index'; +import { geoJoinWays } from '../geo/index'; -export function AddMember(relationId, member, memberIndex) { + +export function actionAddMember(relationId, member, memberIndex) { return function(graph) { var relation = graph.entity(relationId); @@ -8,7 +9,7 @@ export function AddMember(relationId, member, memberIndex) { var members = relation.indexedMembers(); members.push(member); - var joined = joinWays(members, graph); + var joined = geoJoinWays(members, graph); for (var i = 0; i < joined.length; i++) { var segment = joined[i]; for (var j = 0; j < segment.length && segment.length >= 2; j++) { diff --git a/modules/actions/add_midpoint.js b/modules/actions/add_midpoint.js index b3a370617..30f8c8e15 100644 --- a/modules/actions/add_midpoint.js +++ b/modules/actions/add_midpoint.js @@ -1,7 +1,8 @@ import _ from 'lodash'; -import { edgeEqual } from '../geo/index'; +import { geoEdgeEqual } from '../geo/index'; -export function AddMidpoint(midpoint, node) { + +export function actionAddMidpoint(midpoint, node) { return function(graph) { graph = graph.replace(node.move(midpoint.loc)); @@ -11,7 +12,7 @@ export function AddMidpoint(midpoint, node) { parents.forEach(function(way) { for (var i = 0; i < way.nodes.length - 1; i++) { - if (edgeEqual([way.nodes[i], way.nodes[i + 1]], midpoint.edge)) { + if (geoEdgeEqual([way.nodes[i], way.nodes[i + 1]], midpoint.edge)) { graph = graph.replace(graph.entity(way.id).addNode(node.id, i + 1)); // Add only one midpoint on doubled-back segments, diff --git a/modules/actions/add_vertex.js b/modules/actions/add_vertex.js index 29473c286..7ee267566 100644 --- a/modules/actions/add_vertex.js +++ b/modules/actions/add_vertex.js @@ -1,5 +1,5 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as -export function AddVertex(wayId, nodeId, index) { +export function actionAddVertex(wayId, nodeId, index) { return function(graph) { return graph.replace(graph.entity(wayId).addNode(nodeId, index)); }; diff --git a/modules/actions/change_member.js b/modules/actions/change_member.js index 5c2df07c2..896375765 100644 --- a/modules/actions/change_member.js +++ b/modules/actions/change_member.js @@ -1,4 +1,4 @@ -export function ChangeMember(relationId, member, memberIndex) { +export function actionChangeMember(relationId, member, memberIndex) { return function(graph) { return graph.replace(graph.entity(relationId).updateMember(member, memberIndex)); }; diff --git a/modules/actions/change_preset.js b/modules/actions/change_preset.js index d35278d3d..298142b64 100644 --- a/modules/actions/change_preset.js +++ b/modules/actions/change_preset.js @@ -1,4 +1,4 @@ -export function ChangePreset(entityId, oldPreset, newPreset) { +export function actionChangePreset(entityId, oldPreset, newPreset) { return function(graph) { var entity = graph.entity(entityId), geometry = entity.geometry(graph), diff --git a/modules/actions/change_tags.js b/modules/actions/change_tags.js index 703b5bdcb..304488de0 100644 --- a/modules/actions/change_tags.js +++ b/modules/actions/change_tags.js @@ -1,4 +1,4 @@ -export function ChangeTags(entityId, tags) { +export function actionChangeTags(entityId, tags) { return function(graph) { var entity = graph.entity(entityId); return graph.replace(entity.update({tags: tags})); diff --git a/modules/actions/circularize.js b/modules/actions/circularize.js index 8c8d5540b..70cd8544f 100644 --- a/modules/actions/circularize.js +++ b/modules/actions/circularize.js @@ -1,7 +1,7 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { euclideanDistance, interp } from '../geo/index'; -import { Node } from '../core/index'; +import { geoEuclideanDistance, geoInterp } from '../geo/index'; +import { coreNode } from '../core/index'; import { polygonArea as d3polygonArea, @@ -10,9 +10,10 @@ import { } from 'd3'; -export function Circularize(wayId, projection, maxAngle) { +export function actionCircularize(wayId, projection, maxAngle) { maxAngle = (maxAngle || 20) * Math.PI / 180; + var action = function(graph) { var way = graph.entity(wayId); @@ -24,8 +25,8 @@ export function Circularize(wayId, projection, maxAngle) { keyNodes = nodes.filter(function(n) { return graph.parentWays(n).length !== 1; }), points = nodes.map(function(n) { return projection(n.loc); }), keyPoints = keyNodes.map(function(n) { return projection(n.loc); }), - centroid = (points.length === 2) ? interp(points[0], points[1], 0.5) : d3polygonCentroid(points), - radius = d3.median(points, function(p) { return euclideanDistance(centroid, p); }), + centroid = (points.length === 2) ? geoInterp(points[0], points[1], 0.5) : d3polygonCentroid(points), + radius = d3.median(points, function(p) { return geoEuclideanDistance(centroid, p); }), sign = d3polygonArea(points) > 0 ? 1 : -1, ids; @@ -64,7 +65,7 @@ export function Circularize(wayId, projection, maxAngle) { } // position this key node - distance = euclideanDistance(centroid, keyPoints[i]); + distance = geoEuclideanDistance(centroid, keyPoints[i]); if (distance === 0) { distance = 1e-4; } keyPoints[i] = [ centroid[0] + (keyPoints[i][0] - centroid[0]) / distance * radius, @@ -104,7 +105,7 @@ export function Circularize(wayId, projection, maxAngle) { centroid[0] + Math.cos(angle) * radius, centroid[1] + Math.sin(angle) * radius]); - node = Node({loc: loc}); + node = coreNode({loc: loc}); graph = graph.replace(node); nodes.splice(endNodeIndex + j, 0, node); @@ -134,7 +135,7 @@ export function Circularize(wayId, projection, maxAngle) { insertAt = startIndex2; } for (j = 0; j < inBetweenNodes.length; j++) { - sharedWay = sharedWay.addNode(inBetweenNodes[j], insertAt + j); + sharedWay = sharedWay.addcoreNode(inBetweenNodes[j], insertAt + j); } graph = graph.replace(sharedWay); } @@ -154,6 +155,7 @@ export function Circularize(wayId, projection, maxAngle) { return graph; }; + action.makeConvex = function(graph) { var way = graph.entity(wayId), nodes = _.uniq(graph.childNodes(way)), @@ -178,7 +180,7 @@ export function Circularize(wayId, projection, maxAngle) { // move interior nodes to the surface of the convex hull.. for (var j = 1; j < indexRange; j++) { - var point = interp(hull[i], hull[i+1], j / indexRange), + var point = geoInterp(hull[i], hull[i+1], j / indexRange), node = nodes[(j + startIndex) % nodes.length].move(projection.invert(point)); graph = graph.replace(node); } @@ -186,10 +188,12 @@ export function Circularize(wayId, projection, maxAngle) { return graph; }; + action.disabled = function(graph) { if (!graph.entity(wayId).isClosed()) return 'not_closed'; }; + return action; } diff --git a/modules/actions/connect.js b/modules/actions/connect.js index 6437ff52d..03c0d609e 100644 --- a/modules/actions/connect.js +++ b/modules/actions/connect.js @@ -1,5 +1,6 @@ import _ from 'lodash'; -import { DeleteNode } from './delete_node'; +import { actionDeleteNode } from './delete_node'; + // Connect the ways at the given nodes. // @@ -15,7 +16,7 @@ import { DeleteNode } from './delete_node'; // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java // -export function Connect(nodeIds) { +export function actionConnect(nodeIds) { return function(graph) { var survivor = graph.entity(_.last(nodeIds)); @@ -35,7 +36,7 @@ export function Connect(nodeIds) { /* eslint-enable no-loop-func */ survivor = survivor.mergeTags(node.tags); - graph = DeleteNode(node.id)(graph); + graph = actionDeleteNode(node.id)(graph); } graph = graph.replace(survivor); diff --git a/modules/actions/copy_entities.js b/modules/actions/copy_entities.js index 1e87404b9..d47a0a3c7 100644 --- a/modules/actions/copy_entities.js +++ b/modules/actions/copy_entities.js @@ -1,6 +1,7 @@ -export function CopyEntities(ids, fromGraph) { +export function actionCopyEntities(ids, fromGraph) { var copies = {}; + var action = function(graph) { ids.forEach(function(id) { fromGraph.entity(id).copy(fromGraph, copies); @@ -13,9 +14,11 @@ export function CopyEntities(ids, fromGraph) { return graph; }; + action.copies = function() { return copies; }; + return action; } diff --git a/modules/actions/delete_member.js b/modules/actions/delete_member.js index a221615b7..0404508d1 100644 --- a/modules/actions/delete_member.js +++ b/modules/actions/delete_member.js @@ -1,6 +1,7 @@ -import { DeleteRelation } from './delete_relation'; +import { actionDeleteRelation } from './delete_relation'; -export function DeleteMember(relationId, memberIndex) { + +export function actionDeleteMember(relationId, memberIndex) { return function(graph) { var relation = graph.entity(relationId) .removeMember(memberIndex); @@ -8,7 +9,7 @@ export function DeleteMember(relationId, memberIndex) { graph = graph.replace(relation); if (relation.isDegenerate()) - graph = DeleteRelation(relation.id)(graph); + graph = actionDeleteRelation(relation.id)(graph); return graph; }; diff --git a/modules/actions/delete_multiple.js b/modules/actions/delete_multiple.js index 7eaf85519..db90d53ae 100644 --- a/modules/actions/delete_multiple.js +++ b/modules/actions/delete_multiple.js @@ -1,14 +1,16 @@ -import { DeleteNode } from './delete_node'; -import { DeleteRelation } from './delete_relation'; -import { DeleteWay } from './delete_way'; +import { actionDeleteNode } from './delete_node'; +import { actionDeleteRelation } from './delete_relation'; +import { actionDeleteWay } from './delete_way'; -export function DeleteMultiple(ids) { + +export function actionDeleteMultiple(ids) { var actions = { - way: DeleteWay, - node: DeleteNode, - relation: DeleteRelation + way: actionDeleteWay, + node: actionDeleteNode, + relation: actionDeleteRelation }; + var action = function(graph) { ids.forEach(function(id) { if (graph.hasEntity(id)) { // It may have been deleted aready. @@ -19,6 +21,7 @@ export function DeleteMultiple(ids) { return graph; }; + action.disabled = function(graph) { for (var i = 0; i < ids.length; i++) { var id = ids[i], @@ -27,5 +30,6 @@ export function DeleteMultiple(ids) { } }; + return action; } diff --git a/modules/actions/delete_node.js b/modules/actions/delete_node.js index 3097648e6..f8d9d99c3 100644 --- a/modules/actions/delete_node.js +++ b/modules/actions/delete_node.js @@ -1,8 +1,9 @@ -import { DeleteRelation } from './delete_relation'; -import { DeleteWay } from './delete_way'; +import { actionDeleteRelation } from './delete_relation'; +import { actionDeleteWay } from './delete_way'; + // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as -export function DeleteNode(nodeId) { +export function actionDeleteNode(nodeId) { var action = function(graph) { var node = graph.entity(nodeId); @@ -12,7 +13,7 @@ export function DeleteNode(nodeId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteWay(parent.id)(graph); + graph = actionDeleteWay(parent.id)(graph); } }); @@ -22,16 +23,18 @@ export function DeleteNode(nodeId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); return graph.remove(node); }; + action.disabled = function() { return false; }; + return action; } diff --git a/modules/actions/delete_relation.js b/modules/actions/delete_relation.js index ddf3e2d1f..cb74101a6 100644 --- a/modules/actions/delete_relation.js +++ b/modules/actions/delete_relation.js @@ -1,14 +1,18 @@ import _ from 'lodash'; -import { DeleteMultiple } from './delete_multiple'; +import { actionDeleteMultiple } from './delete_multiple'; + // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as -export function DeleteRelation(relationId) { - function deleteEntity(entity, graph) { +export function actionDeleteRelation(relationId) { + + + function canDeleteEntity(entity, graph) { return !graph.parentWays(entity).length && !graph.parentRelations(entity).length && !entity.hasInterestingTags(); } + var action = function(graph) { var relation = graph.entity(relationId); @@ -18,7 +22,7 @@ export function DeleteRelation(relationId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); @@ -26,18 +30,20 @@ export function DeleteRelation(relationId) { graph = graph.replace(relation.removeMembersWithID(memberId)); var entity = graph.entity(memberId); - if (deleteEntity(entity, graph)) { - graph = DeleteMultiple([memberId])(graph); + if (canDeleteEntity(entity, graph)) { + graph = actionDeleteMultiple([memberId])(graph); } }); return graph.remove(relation); }; + action.disabled = function(graph) { if (!graph.entity(relationId).isComplete(graph)) return 'incomplete_relation'; }; + return action; } diff --git a/modules/actions/delete_way.js b/modules/actions/delete_way.js index 61ff68b75..1917cd765 100644 --- a/modules/actions/delete_way.js +++ b/modules/actions/delete_way.js @@ -1,14 +1,18 @@ import _ from 'lodash'; -import { DeleteRelation } from './delete_relation'; +import { actionDeleteRelation } from './delete_relation'; + // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as -export function DeleteWay(wayId) { - function deleteNode(node, graph) { +export function actionDeleteWay(wayId) { + + + function canDeleteNode(node, graph) { return !graph.parentWays(node).length && !graph.parentRelations(node).length && !node.hasInterestingTags(); } + var action = function(graph) { var way = graph.entity(wayId); @@ -18,7 +22,7 @@ export function DeleteWay(wayId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); @@ -26,7 +30,7 @@ export function DeleteWay(wayId) { graph = graph.replace(way.removeNode(nodeId)); var node = graph.entity(nodeId); - if (deleteNode(node, graph)) { + if (canDeleteNode(node, graph)) { graph = graph.remove(node); } }); @@ -34,6 +38,7 @@ export function DeleteWay(wayId) { return graph.remove(way); }; + action.disabled = function(graph) { var disabled = false; @@ -48,5 +53,6 @@ export function DeleteWay(wayId) { return disabled; }; + return action; } diff --git a/modules/actions/deprecate_tags.js b/modules/actions/deprecate_tags.js index f05ffc80c..e04f7ff7e 100644 --- a/modules/actions/deprecate_tags.js +++ b/modules/actions/deprecate_tags.js @@ -1,17 +1,19 @@ import _ from 'lodash'; -import { deprecated } from '../../data/index'; +import { dataDeprecated } from '../../data/index'; + + +export function actionDeprecateTags(entityId) { -export function DeprecateTags(entityId) { return function(graph) { var entity = graph.entity(entityId), newtags = _.clone(entity.tags), change = false, rule; - // This handles deprecated tags with a single condition - for (var i = 0; i < deprecated.length; i++) { + // This handles dataDeprecated tags with a single condition + for (var i = 0; i < dataDeprecated.length; i++) { - rule = deprecated[i]; + rule = dataDeprecated[i]; var match = _.toPairs(rule.old)[0], replacements = rule.replace ? _.toPairs(rule.replace) : null; diff --git a/modules/actions/discard_tags.js b/modules/actions/discard_tags.js index 76b6e93eb..ab34306de 100644 --- a/modules/actions/discard_tags.js +++ b/modules/actions/discard_tags.js @@ -1,7 +1,9 @@ import _ from 'lodash'; -import { discarded } from '../../data/index'; +import { dataDiscarded } from '../../data/index'; + + +export function actionDiscardTags(difference) { -export function DiscardTags(difference) { return function(graph) { function discardTags(entity) { if (!_.isEmpty(entity.tags)) { @@ -11,7 +13,7 @@ export function DiscardTags(difference) { }); graph = graph.replace(entity.update({ - tags: _.omit(tags, discarded) + tags: _.omit(tags, dataDiscarded) })); } } diff --git a/modules/actions/disconnect.js b/modules/actions/disconnect.js index f96fcf0f8..1c652f7f4 100644 --- a/modules/actions/disconnect.js +++ b/modules/actions/disconnect.js @@ -1,4 +1,5 @@ -import { Node } from '../core/index'; +import { coreNode } from '../core/index'; + // Disconect the ways at the given node. // @@ -14,16 +15,17 @@ import { Node } from '../core/index'; // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java // -export function Disconnect(nodeId, newNodeId) { +export function actionDisconnect(nodeId, newNodeId) { var wayIds; + var action = function(graph) { var node = graph.entity(nodeId), connections = action.connections(graph); connections.forEach(function(connection) { var way = graph.entity(connection.wayID), - newNode = Node({id: newNodeId, loc: node.loc, tags: node.tags}); + newNode = coreNode({id: newNodeId, loc: node.loc, tags: node.tags}); graph = graph.replace(newNode); if (connection.index === 0 && way.isArea()) { @@ -38,6 +40,7 @@ export function Disconnect(nodeId, newNodeId) { return graph; }; + action.connections = function(graph) { var candidates = [], keeping = false, @@ -62,6 +65,7 @@ export function Disconnect(nodeId, newNodeId) { return keeping ? candidates : candidates.slice(1); }; + action.disabled = function(graph) { var connections = action.connections(graph); if (connections.length === 0 || (wayIds && wayIds.length !== connections.length)) @@ -89,11 +93,13 @@ export function Disconnect(nodeId, newNodeId) { return 'relation'; }; + action.limitWays = function(_) { if (!arguments.length) return wayIds; wayIds = _; return action; }; + return action; } diff --git a/modules/actions/index.js b/modules/actions/index.js index 10a772d0e..afb314a94 100644 --- a/modules/actions/index.js +++ b/modules/actions/index.js @@ -1,33 +1,33 @@ -export { AddEntity } from './add_entity'; -export { AddMember } from './add_member'; -export { AddMidpoint } from './add_midpoint'; -export { AddVertex } from './add_vertex'; -export { ChangeMember } from './change_member'; -export { ChangePreset } from './change_preset'; -export { ChangeTags } from './change_tags'; -export { Circularize } from './circularize'; -export { Connect } from './connect'; -export { CopyEntities } from './copy_entities'; -export { DeleteMember } from './delete_member'; -export { DeleteMultiple } from './delete_multiple'; -export { DeleteNode } from './delete_node'; -export { DeleteRelation } from './delete_relation'; -export { DeleteWay } from './delete_way'; -export { DeprecateTags } from './deprecate_tags'; -export { DiscardTags } from './discard_tags'; -export { Disconnect } from './disconnect'; -export { Join } from './join'; -export { Merge } from './merge'; -export { MergePolygon } from './merge_polygon'; -export { MergeRemoteChanges } from './merge_remote_changes'; -export { Move } from './move'; -export { MoveNode } from './move_node'; -export { Noop } from './noop'; -export { Orthogonalize } from './orthogonalize'; -export { RestrictTurn } from './restrict_turn'; -export { Reverse } from './reverse'; -export { Revert } from './revert'; -export { RotateWay } from './rotate_way'; -export { Split } from './split'; -export { Straighten } from './straighten'; -export { UnrestrictTurn } from './unrestrict_turn'; +export { actionAddEntity } from './add_entity'; +export { actionAddMember } from './add_member'; +export { actionAddMidpoint } from './add_midpoint'; +export { actionAddVertex } from './add_vertex'; +export { actionChangeMember } from './change_member'; +export { actionChangePreset } from './change_preset'; +export { actionChangeTags } from './change_tags'; +export { actionCircularize } from './circularize'; +export { actionConnect } from './connect'; +export { actionCopyEntities } from './copy_entities'; +export { actionDeleteMember } from './delete_member'; +export { actionDeleteMultiple } from './delete_multiple'; +export { actionDeleteNode } from './delete_node'; +export { actionDeleteRelation } from './delete_relation'; +export { actionDeleteWay } from './delete_way'; +export { actionDeprecateTags } from './deprecate_tags'; +export { actionDiscardTags } from './discard_tags'; +export { actionDisconnect } from './disconnect'; +export { actionJoin } from './join'; +export { actionMerge } from './merge'; +export { actionMergePolygon } from './merge_polygon'; +export { actionMergeRemoteChanges } from './merge_remote_changes'; +export { actionMove } from './move'; +export { actionMoveNode } from './move_node'; +export { actionNoop } from './noop'; +export { actionOrthogonalize } from './orthogonalize'; +export { actionRestrictTurn } from './restrict_turn'; +export { actionReverse } from './reverse'; +export { actionRevert } from './revert'; +export { actionRotateWay } from './rotate_way'; +export { actionSplit } from './split'; +export { actionStraighten } from './straighten'; +export { actionUnrestrictTurn } from './unrestrict_turn'; diff --git a/modules/actions/join.js b/modules/actions/join.js index a9b0afaf5..994b45ea0 100644 --- a/modules/actions/join.js +++ b/modules/actions/join.js @@ -1,7 +1,8 @@ import _ from 'lodash'; -import { DeleteWay } from './delete_way'; -import { interestingTag } from '../core/index'; -import { joinWays } from '../geo/index'; +import { actionDeleteWay } from './delete_way'; +import { coreInterestingTag } from '../core/index'; +import { geoJoinWays } from '../geo/index'; + // Join ways at the end node they share. // @@ -11,13 +12,14 @@ import { joinWays } from '../geo/index'; // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java // -export function Join(ids) { +export function actionJoin(ids) { function groupEntitiesByGeometry(graph) { var entities = ids.map(function(id) { return graph.entity(id); }); return _.extend({line: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); })); } + var action = function(graph) { var ways = ids.map(graph.entity, graph), survivor = ways[0]; @@ -30,7 +32,7 @@ export function Join(ids) { } } - var joined = joinWays(ways, graph)[0]; + var joined = geoJoinWays(ways, graph)[0]; survivor = survivor.update({nodes: _.map(joined.nodes, 'id')}); graph = graph.replace(survivor); @@ -46,18 +48,19 @@ export function Join(ids) { survivor = survivor.mergeTags(way.tags); graph = graph.replace(survivor); - graph = DeleteWay(way.id)(graph); + graph = actionDeleteWay(way.id)(graph); }); return graph; }; + action.disabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); if (ids.length < 2 || ids.length !== geometries.line.length) return 'not_eligible'; - var joined = joinWays(ids.map(graph.entity, graph), graph); + var joined = geoJoinWays(ids.map(graph.entity, graph), graph); if (joined.length > 1) return 'not_adjacent'; @@ -76,7 +79,7 @@ export function Join(ids) { for (var k in way.tags) { if (!(k in tags)) { tags[k] = way.tags[k]; - } else if (tags[k] && interestingTag(k) && tags[k] !== way.tags[k]) { + } else if (tags[k] && coreInterestingTag(k) && tags[k] !== way.tags[k]) { conflicting = true; } } @@ -89,5 +92,6 @@ export function Join(ids) { return 'conflicting_tags'; }; + return action; } diff --git a/modules/actions/merge.js b/modules/actions/merge.js index 5aae60cfc..a2ff40ffc 100644 --- a/modules/actions/merge.js +++ b/modules/actions/merge.js @@ -1,12 +1,15 @@ import _ from 'lodash'; -export function Merge(ids) { + +export function actionMerge(ids) { + function groupEntitiesByGeometry(graph) { var entities = ids.map(function(id) { return graph.entity(id); }); return _.extend({point: [], area: [], line: [], relation: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); })); } + var action = function(graph) { var geometries = groupEntitiesByGeometry(graph), target = geometries.area[0] || geometries.line[0], @@ -27,6 +30,7 @@ export function Merge(ids) { return graph; }; + action.disabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); if (geometries.point.length === 0 || @@ -35,5 +39,6 @@ export function Merge(ids) { return 'not_eligible'; }; + return action; } diff --git a/modules/actions/merge_polygon.js b/modules/actions/merge_polygon.js index d5c0aa0d1..1e162f60f 100644 --- a/modules/actions/merge_polygon.js +++ b/modules/actions/merge_polygon.js @@ -1,8 +1,9 @@ import _ from 'lodash'; -import { joinWays, polygonContainsPolygon } from '../geo/index'; -import { Relation } from '../core/index'; +import { geoJoinWays, geoPolygonContainsPolygon } from '../geo/index'; +import { coreRelation } from '../core/index'; -export function MergePolygon(ids, newRelationId) { + +export function actionMergePolygon(ids, newRelationId) { function groupEntities(graph) { var entities = ids.map(function (id) { return graph.entity(id); }); @@ -21,6 +22,7 @@ export function MergePolygon(ids, newRelationId) { })); } + var action = function(graph) { var entities = groupEntities(graph); @@ -29,7 +31,7 @@ export function MergePolygon(ids, newRelationId) { // Each element is itself an array of objects with an id property, and has a // locs property which is an array of the locations forming the polygon. var polygons = entities.multipolygon.reduce(function(polygons, m) { - return polygons.concat(joinWays(m.members, graph)); + return polygons.concat(geoJoinWays(m.members, graph)); }, []).concat(entities.closedWay.map(function(d) { var member = [{id: d.id}]; member.nodes = graph.childNodes(d); @@ -42,7 +44,7 @@ export function MergePolygon(ids, newRelationId) { var contained = polygons.map(function(w, i) { return polygons.map(function(d, n) { if (i === n) return null; - return polygonContainsPolygon( + return geoPolygonContainsPolygon( _.map(d.nodes, 'loc'), _.map(w.nodes, 'loc')); }); @@ -83,7 +85,7 @@ export function MergePolygon(ids, newRelationId) { // Move all tags to one relation var relation = entities.multipolygon[0] || - Relation({ id: newRelationId, tags: { type: 'multipolygon' }}); + coreRelation({ id: newRelationId, tags: { type: 'multipolygon' }}); entities.multipolygon.slice(1).forEach(function(m) { relation = relation.mergeTags(m.tags); @@ -106,6 +108,7 @@ export function MergePolygon(ids, newRelationId) { })); }; + action.disabled = function(graph) { var entities = groupEntities(graph); if (entities.other.length > 0 || @@ -115,5 +118,6 @@ export function MergePolygon(ids, newRelationId) { return 'incomplete_relation'; }; + return action; } diff --git a/modules/actions/merge_remote_changes.js b/modules/actions/merge_remote_changes.js index c130a06fc..34b0f3b2a 100644 --- a/modules/actions/merge_remote_changes.js +++ b/modules/actions/merge_remote_changes.js @@ -1,14 +1,16 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { DeleteMultiple } from './delete_multiple'; -import { Entity } from '../core/index'; +import { actionDeleteMultiple } from './delete_multiple'; +import { coreEntity } from '../core/index'; import { diff3_merge } from '../util/diff3'; -import { discarded } from '../../data/index'; +import { dataDiscarded } from '../../data/index'; -export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { + +export function actionMergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { var option = 'safe', // 'safe', 'force_local', 'force_remote' conflicts = []; + function user(d) { return _.isFunction(formatUser) ? formatUser(d) : d; } @@ -101,14 +103,14 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { updates.replacements.push(remote); } else if (option === 'force_local' && local) { - target = Entity(local); + target = coreEntity(local); if (remote) { target = target.update({ version: remote.version }); } updates.replacements.push(target); } else if (option === 'safe' && local && remote && local.version !== remote.version) { - target = Entity(local, { version: remote.version }); + target = coreEntity(local, { version: remote.version }); if (remote.visible) { target = mergeLocation(remote, target); } else { @@ -129,7 +131,7 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { graph = graph.replace(updates.replacements[i]); } if (updates.removeIds.length) { - graph = DeleteMultiple(updates.removeIds)(graph); + graph = actionDeleteMultiple(updates.removeIds)(graph); } return graph; } @@ -150,7 +152,7 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { function mergeTags(base, remote, target) { function ignoreKey(k) { - return _.includes(discarded, k); + return _.includes(dataDiscarded, k); } if (option === 'force_local' || _.isEqual(target.tags, remote.tags)) { @@ -206,7 +208,7 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { base = graph.base().entities[id], local = localGraph.entity(id), remote = remoteGraph.entity(id), - target = Entity(local, { version: remote.version }); + target = coreEntity(local, { version: remote.version }); // delete/undelete if (!remote.visible) { @@ -249,14 +251,17 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { return graph; }; + action.withOption = function(opt) { option = opt; return action; }; + action.conflicts = function() { return conflicts; }; + return action; } diff --git a/modules/actions/move.js b/modules/actions/move.js index 23fa57755..e07f93e26 100644 --- a/modules/actions/move.js +++ b/modules/actions/move.js @@ -1,17 +1,18 @@ import _ from 'lodash'; -import { Node } from '../core/index'; +import { coreNode } from '../core/index'; import { - chooseEdge, - angle as getAngle, - interp, - pathIntersections, - pathLength, - sphericalDistance + geoChooseEdge, + geoAngle, + geoInterp, + geoPathIntersections, + geoPathLength, + geoSphericalDistance } from '../geo/index'; + // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as -export function Move(moveIds, tryDelta, projection, cache) { +export function actionMove(moveIds, tryDelta, projection, cache) { var delta = tryDelta; function vecAdd(a, b) { return [a[0] + b[0], a[1] + b[1]]; } @@ -132,7 +133,7 @@ export function Move(moveIds, tryDelta, projection, cache) { var key = wayId + '_' + nodeId, orig = cache.replacedVertex[key]; if (!orig) { - orig = Node(); + orig = coreNode(); cache.replacedVertex[key] = orig; cache.startLoc[orig.id] = cache.startLoc[nodeId]; } @@ -146,21 +147,21 @@ export function Move(moveIds, tryDelta, projection, cache) { } orig = orig.move(end); - var angle = Math.abs(getAngle(orig, prev, projection) - - getAngle(orig, next, projection)) * 180 / Math.PI; + var angle = Math.abs(geoAngle(orig, prev, projection) - + geoAngle(orig, next, projection)) * 180 / Math.PI; // Don't add orig vertex if it would just make a straight line.. if (angle > 175 && angle < 185) return graph; // Don't add orig vertex if another point is already nearby (within 10m) - if (sphericalDistance(prev.loc, orig.loc) < 10 || - sphericalDistance(orig.loc, next.loc) < 10) return graph; + if (geoSphericalDistance(prev.loc, orig.loc) < 10 || + geoSphericalDistance(orig.loc, next.loc) < 10) return graph; // moving forward or backward along way? var p1 = [prev.loc, orig.loc, moved.loc, next.loc].map(projection), p2 = [prev.loc, moved.loc, orig.loc, next.loc].map(projection), - d1 = pathLength(p1), - d2 = pathLength(p2), + d1 = geoPathLength(p1), + d2 = geoPathLength(p2), insertAt = (d1 < d2) ? movedIndex : nextIndex; // moving around closed loop? @@ -170,6 +171,7 @@ export function Move(moveIds, tryDelta, projection, cache) { return graph.replace(orig).replace(way); } + // Reorder nodes around intersections that have moved.. function unZorroIntersection(intersection, graph) { var vertex = graph.entity(intersection.nodeId), @@ -187,17 +189,17 @@ export function Move(moveIds, tryDelta, projection, cache) { if (way1.isClosed() && way1.first() === vertex.id) nodes1.push(nodes1[0]); if (way2.isClosed() && way2.first() === vertex.id) nodes2.push(nodes2[0]); - var edge1 = !isEP1 && chooseEdge(nodes1, projection(vertex.loc), projection), - edge2 = !isEP2 && chooseEdge(nodes2, projection(vertex.loc), projection), + var edge1 = !isEP1 && geoChooseEdge(nodes1, projection(vertex.loc), projection), + edge2 = !isEP2 && geoChooseEdge(nodes2, projection(vertex.loc), projection), loc; // snap vertex to nearest edge (or some point between them).. if (!isEP1 && !isEP2) { var epsilon = 1e-4, maxIter = 10; for (var i = 0; i < maxIter; i++) { - loc = interp(edge1.loc, edge2.loc, 0.5); - edge1 = chooseEdge(nodes1, projection(loc), projection); - edge2 = chooseEdge(nodes2, projection(loc), projection); + loc = geoInterp(edge1.loc, edge2.loc, 0.5); + edge1 = geoChooseEdge(nodes1, projection(loc), projection); + edge2 = geoChooseEdge(nodes2, projection(loc), projection); if (Math.abs(edge1.distance - edge2.distance) < epsilon) break; } } else if (!isEP1) { @@ -232,6 +234,7 @@ export function Move(moveIds, tryDelta, projection, cache) { return graph; } + // check if moving way endpoint can cross an unmoved way, if so limit delta.. function limitDelta(graph) { _.each(cache.intersection, function(obj) { @@ -248,11 +251,11 @@ export function Move(moveIds, tryDelta, projection, cache) { function(loc) { return vecAdd(projection(loc), delta); }), unmovedNodes = graph.childNodes(graph.entity(obj.unmovedId)), unmovedPath = _.map(_.map(unmovedNodes, 'loc'), projection), - hits = pathIntersections(movedPath, unmovedPath); + hits = geoPathIntersections(movedPath, unmovedPath); for (var i = 0; i < hits.length; i++) { if (_.isEqual(hits[i], end)) continue; - var edge = chooseEdge(unmovedNodes, end, projection); + var edge = geoChooseEdge(unmovedNodes, end, projection); delta = vecSub(projection(edge.loc), start); } }); @@ -282,6 +285,7 @@ export function Move(moveIds, tryDelta, projection, cache) { return graph; }; + action.disabled = function(graph) { function incompleteRelation(id) { var entity = graph.entity(id); @@ -292,9 +296,11 @@ export function Move(moveIds, tryDelta, projection, cache) { return 'incomplete_relation'; }; + action.delta = function() { return delta; }; + return action; } diff --git a/modules/actions/move_node.js b/modules/actions/move_node.js index 0fa702d02..6e2593b45 100644 --- a/modules/actions/move_node.js +++ b/modules/actions/move_node.js @@ -1,6 +1,6 @@ // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as -export function MoveNode(nodeId, loc) { +export function actionMoveNode(nodeId, loc) { return function(graph) { return graph.replace(graph.entity(nodeId).move(loc)); }; diff --git a/modules/actions/noop.js b/modules/actions/noop.js index 573cd535b..996323e13 100644 --- a/modules/actions/noop.js +++ b/modules/actions/noop.js @@ -1,4 +1,4 @@ -export function Noop() { +export function actionNoop() { return function(graph) { return graph; }; diff --git a/modules/actions/orthogonalize.js b/modules/actions/orthogonalize.js index 983c8dfc0..b3c311b33 100644 --- a/modules/actions/orthogonalize.js +++ b/modules/actions/orthogonalize.js @@ -1,15 +1,16 @@ import _ from 'lodash'; -import { DeleteNode } from './delete_node'; -import { euclideanDistance } from '../geo/index'; +import { actionDeleteNode } from './delete_node'; +import { geoEuclideanDistance } from '../geo/index'; /* * Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as */ -export function Orthogonalize(wayId, projection) { +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); + var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), @@ -30,6 +31,7 @@ export function Orthogonalize(wayId, projection) { graph = graph.replace(graph.entity(nodes[corner.i].id) .move(projection.invert(points[corner.i]))); + } else { var best, originalPoints = _.clone(points); @@ -73,13 +75,14 @@ export function Orthogonalize(wayId, projection) { var dotp = normalizedDotProduct(i, points); if (dotp < -1 + epsilon) { - graph = DeleteNode(nodes[i].id)(graph); + graph = actionDeleteNode(nodes[i].id)(graph); } } } return graph; + function calcMotion(b, i, array) { var a = array[(i - 1 + array.length) % array.length], c = array[(i + 1) % array.length], @@ -87,7 +90,7 @@ export function Orthogonalize(wayId, projection) { q = subtractPoints(c, b), scale, dotp; - scale = 2 * Math.min(euclideanDistance(p, [0, 0]), euclideanDistance(q, [0, 0])); + scale = 2 * Math.min(geoEuclideanDistance(p, [0, 0]), geoEuclideanDistance(q, [0, 0])); p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); @@ -107,6 +110,7 @@ export function Orthogonalize(wayId, projection) { } }; + function squareness(points) { return points.reduce(function(sum, val, i, array) { var dotp = normalizedDotProduct(i, array); @@ -116,6 +120,7 @@ export function Orthogonalize(wayId, projection) { }, 0); } + function normalizedDotProduct(i, points) { var a = points[(i - 1 + points.length) % points.length], b = points[i], @@ -129,14 +134,17 @@ export function Orthogonalize(wayId, projection) { 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]); @@ -151,6 +159,7 @@ export function Orthogonalize(wayId, projection) { return vector; } + function filterDotProduct(dotp) { if (lowerThreshold > Math.abs(dotp) || Math.abs(dotp) > upperThreshold) { return dotp; @@ -159,6 +168,7 @@ export function Orthogonalize(wayId, projection) { return 0; } + action.disabled = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), @@ -171,5 +181,6 @@ export function Orthogonalize(wayId, projection) { return 'not_squarish'; }; + return action; } diff --git a/modules/actions/restrict_turn.js b/modules/actions/restrict_turn.js index 0afc7c68a..88644b05d 100644 --- a/modules/actions/restrict_turn.js +++ b/modules/actions/restrict_turn.js @@ -1,6 +1,7 @@ -import { Relation, Way } from '../core/index'; -import { Split } from './split'; -import { inferRestriction } from '../geo/index'; +import { coreRelation, coreWay } from '../core/index'; +import { actionSplit } from './split'; +import { geoInferRestriction } from '../geo/index'; + // Create a restriction relation for `turn`, which must have the following structure: // @@ -16,7 +17,7 @@ import { inferRestriction } from '../geo/index'; // (The action does not check that these entities form a valid intersection.) // // If `restriction` is not provided, it is automatically determined by -// inferRestriction. +// geoInferRestriction. // // If necessary, the `from` and `to` ways are split. In these cases, `from.node` // and `to.node` are used to determine which portion of the split ways become @@ -26,7 +27,8 @@ import { inferRestriction } from '../geo/index'; // Normally, this will be undefined and the relation will automatically // be assigned a new ID. // -export function RestrictTurn(turn, projection, restrictionId) { +export function actionRestrictTurn(turn, projection, restrictionId) { + return function(graph) { var from = graph.entity(turn.from.way), via = graph.entity(turn.via.node), @@ -37,8 +39,8 @@ export function RestrictTurn(turn, projection, restrictionId) { } function split(toOrFrom) { - var newID = toOrFrom.newID || Way().id; - graph = Split(via.id, [newID]) + var newID = toOrFrom.newID || coreWay().id; + graph = actionSplit(via.id, [newID]) .limitWays([toOrFrom.way])(graph); var a = graph.entity(newID), @@ -70,12 +72,12 @@ export function RestrictTurn(turn, projection, restrictionId) { to = split(turn.to)[0]; } - return graph.replace(Relation({ + return graph.replace(coreRelation({ id: restrictionId, tags: { type: 'restriction', restriction: turn.restriction || - inferRestriction( + geoInferRestriction( graph, turn.from, turn.via, diff --git a/modules/actions/reverse.js b/modules/actions/reverse.js index 1fb18ad85..cd1eda959 100644 --- a/modules/actions/reverse.js +++ b/modules/actions/reverse.js @@ -29,7 +29,7 @@ http://wiki.openstreetmap.org/wiki/Route#Members http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java */ -export function Reverse(wayId, options) { +export function actionReverse(wayId, options) { var replacements = [ [/:right$/, ':left'], [/:left$/, ':right'], [/:forward$/, ':backward'], [/:backward$/, ':forward'] @@ -44,6 +44,7 @@ export function Reverse(wayId, options) { west: 'east' }; + function reverseKey(key) { for (var i = 0; i < replacements.length; ++i) { var replacement = replacements[i]; @@ -54,6 +55,7 @@ export function Reverse(wayId, options) { return key; } + function reverseValue(key, value) { if (key === 'incline' && numeric.test(value)) { return value.replace(numeric, function(_, sign) { return sign === '-' ? '' : '-'; }); @@ -66,6 +68,7 @@ export function Reverse(wayId, options) { } } + return function(graph) { var way = graph.entity(wayId), nodes = way.nodes.slice().reverse(), diff --git a/modules/actions/revert.js b/modules/actions/revert.js index 1580bd595..018fb709e 100644 --- a/modules/actions/revert.js +++ b/modules/actions/revert.js @@ -1,7 +1,8 @@ -import { DeleteRelation } from './delete_relation'; -import { DeleteWay } from './delete_way'; +import { actionDeleteRelation } from './delete_relation'; +import { actionDeleteWay } from './delete_way'; -export function Revert(id) { + +export function actionRevert(id) { var action = function(graph) { var entity = graph.hasEntity(id), base = graph.base().entities[id]; @@ -14,7 +15,7 @@ export function Revert(id) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteWay(parent.id)(graph); + graph = actionDeleteWay(parent.id)(graph); } }); } @@ -25,7 +26,7 @@ export function Revert(id) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); } diff --git a/modules/actions/rotate_way.js b/modules/actions/rotate_way.js index 539d73714..6f259da22 100644 --- a/modules/actions/rotate_way.js +++ b/modules/actions/rotate_way.js @@ -1,6 +1,7 @@ import _ from 'lodash'; -export function RotateWay(wayId, pivot, angle, projection) { + +export function actionRotateWay(wayId, pivot, angle, projection) { return function(graph) { return graph.update(function(graph) { var way = graph.entity(wayId); diff --git a/modules/actions/split.js b/modules/actions/split.js index 87f8b9f37..0ec0d2831 100644 --- a/modules/actions/split.js +++ b/modules/actions/split.js @@ -1,8 +1,9 @@ import _ from 'lodash'; -import { Relation, Way } from '../core/index'; -import { isSimpleMultipolygonOuterMember, sphericalDistance } from '../geo/index'; -import { AddMember } from './add_member'; -import { wrap as Wrap } from '../util/index'; +import { coreRelation, coreWay } from '../core/index'; +import { geoIsSimpleMultipolygonOuterMember, geoSphericalDistance } from '../geo/index'; +import { actionAddMember } from './add_member'; +import { utilWrap } from '../util/index'; + // Split a way at the given node. // @@ -18,7 +19,7 @@ import { wrap as Wrap } from '../util/index'; // Reference: // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // -export function Split(nodeId, newWayIds) { +export function actionSplit(nodeId, newWayIds) { var wayIds; // if the way is closed, we need to search for a partner node @@ -39,11 +40,11 @@ export function Split(nodeId, newWayIds) { idxB; function wrap(index) { - return Wrap(index, nodes.length); + return utilWrap(index, nodes.length); } function dist(nA, nB) { - return sphericalDistance(graph.entity(nA).loc, graph.entity(nB).loc); + return geoSphericalDistance(graph.entity(nA).loc, graph.entity(nB).loc); } // calculate lengths @@ -72,12 +73,13 @@ export function Split(nodeId, newWayIds) { return idxB; } + function split(graph, wayA, newWayId) { - var wayB = Way({id: newWayId, tags: wayA.tags}), + var wayB = coreWay({id: newWayId, tags: wayA.tags}), nodesA, nodesB, isArea = wayA.isArea(), - isOuter = isSimpleMultipolygonOuterMember(wayA, graph); + isOuter = geoIsSimpleMultipolygonOuterMember(wayA, graph); if (wayA.isClosed()) { var nodes = wayA.nodes.slice(0, -1), @@ -123,12 +125,12 @@ export function Split(nodeId, newWayIds) { role: relation.memberById(wayA.id).role }; - graph = AddMember(relation.id, member)(graph); + graph = actionAddMember(relation.id, member)(graph); } }); if (!isOuter && isArea) { - var multipolygon = Relation({ + var multipolygon = coreRelation({ tags: _.extend({}, wayA.tags, {type: 'multipolygon'}), members: [ {id: wayA.id, role: 'outer', type: 'way'}, @@ -143,6 +145,7 @@ export function Split(nodeId, newWayIds) { return graph; } + var action = function(graph) { var candidates = action.ways(graph); for (var i = 0; i < candidates.length; i++) { @@ -151,6 +154,7 @@ export function Split(nodeId, newWayIds) { return graph; }; + action.ways = function(graph) { var node = graph.entity(nodeId), parents = graph.parentWays(node), @@ -177,17 +181,20 @@ export function Split(nodeId, newWayIds) { }); }; + action.disabled = function(graph) { var candidates = action.ways(graph); if (candidates.length === 0 || (wayIds && wayIds.length !== candidates.length)) return 'not_eligible'; }; + action.limitWays = function(_) { if (!arguments.length) return wayIds; wayIds = _; return action; }; + return action; } diff --git a/modules/actions/straighten.js b/modules/actions/straighten.js index a66ec0a24..c7de86011 100644 --- a/modules/actions/straighten.js +++ b/modules/actions/straighten.js @@ -1,14 +1,17 @@ -import { DeleteNode } from './delete_node'; +import { actionDeleteNode } from './delete_node'; + /* * Based on https://github.com/openstreetmap/potlatch2/net/systemeD/potlatch2/tools/Straighten.as */ -export function Straighten(wayId, projection) { +export function actionStraighten(wayId, projection) { + function positionAlongWay(n, s, e) { return ((n[0] - s[0]) * (e[0] - s[0]) + (n[1] - s[1]) * (e[1] - s[1]))/ (Math.pow(e[0] - s[0], 2) + Math.pow(e[1] - s[1], 2)); } + var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), @@ -30,8 +33,10 @@ export function Straighten(wayId, projection) { p0 = startPoint[0] + u * (endPoint[0] - startPoint[0]), p1 = startPoint[1] + u * (endPoint[1] - startPoint[1]); - graph = graph.replace(graph.entity(node.id) + graph = graph + .replace(graph.entity(node.id) .move(projection.invert([p0, p1]))); + } else { // safe to delete if (toDelete.indexOf(node) === -1) { @@ -41,12 +46,13 @@ export function Straighten(wayId, projection) { } for (i = 0; i < toDelete.length; i++) { - graph = DeleteNode(toDelete[i].id)(graph); + graph = actionDeleteNode(toDelete[i].id)(graph); } return graph; }; + action.disabled = function(graph) { // check way isn't too bendy var way = graph.entity(wayId), @@ -75,5 +81,6 @@ export function Straighten(wayId, projection) { } }; + return action; } diff --git a/modules/actions/unrestrict_turn.js b/modules/actions/unrestrict_turn.js index 6535372b9..3e0a794c5 100644 --- a/modules/actions/unrestrict_turn.js +++ b/modules/actions/unrestrict_turn.js @@ -1,4 +1,5 @@ -import { DeleteRelation } from './delete_relation'; +import { actionDeleteRelation } from './delete_relation'; + // Remove the effects of `turn.restriction` on `turn`, which must have the // following structure: @@ -18,8 +19,8 @@ import { DeleteRelation } from './delete_relation'; // that restriction is also deleted, but at the same time restrictions on // the turns other than the first two are created. // -export function UnrestrictTurn(turn) { +export function actionUnrestrictTurn(turn) { return function(graph) { - return DeleteRelation(turn.restriction)(graph); + return actionDeleteRelation(turn.restriction)(graph); }; } diff --git a/modules/behavior/add_way.js b/modules/behavior/add_way.js index 476a6d779..fe0d34a79 100644 --- a/modules/behavior/add_way.js +++ b/modules/behavior/add_way.js @@ -1,11 +1,12 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; -import { Browse } from '../modes/index'; -import { Draw } from './draw'; +import { utilRebind } from '../util/rebind'; +import { modeBrowse } from '../modes/index'; +import { behaviorDraw } from './draw'; -export function AddWay(context) { + +export function behaviorAddWay(context) { var dispatch = d3.dispatch('start', 'startFromWay', 'startFromNode'), - draw = Draw(context); + draw = behaviorDraw(context); var addWay = function(surface) { draw.on('click', function() { dispatch.apply('start', this, arguments); }) @@ -20,22 +21,26 @@ export function AddWay(context) { surface.call(draw); }; + addWay.off = function(surface) { surface.call(draw.off); }; + addWay.cancel = function() { window.setTimeout(function() { context.map().dblclickEnable(true); }, 1000); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); }; + addWay.tail = function(text) { draw.tail(text); return addWay; }; - return rebind(addWay, dispatch, 'on'); + + return utilRebind(addWay, dispatch, 'on'); } diff --git a/modules/behavior/breathe.js b/modules/behavior/breathe.js index 51fb7f3ec..85a494ea7 100644 --- a/modules/behavior/breathe.js +++ b/modules/behavior/breathe.js @@ -3,7 +3,7 @@ import { transition as d3transition } from 'd3'; import _ from 'lodash'; -export function Breathe() { +export function behaviorBreathe() { var duration = 800, steps = 4, selector = '.selected.shadow, .selected .shadow', diff --git a/modules/behavior/copy.js b/modules/behavior/copy.js index 3f7e4e52c..5f79f1a7e 100644 --- a/modules/behavior/copy.js +++ b/modules/behavior/copy.js @@ -1,17 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { cmd } from '../ui/index'; +import { uiCmd } from '../ui/index'; -export function Copy(context) { + +export function behaviorCopy(context) { var keybinding = d3keybinding('copy'); + function groupEntities(ids, graph) { var entities = ids.map(function (id) { return graph.entity(id); }); return _.extend({relation: [], way: [], node: []}, _.groupBy(entities, function(entity) { return entity.type; })); } + function getDescendants(id, graph, descendants) { var entity = graph.entity(id), i, children; @@ -36,6 +39,7 @@ export function Copy(context) { return descendants; } + function doCopy() { d3.event.preventDefault(); if (context.inIntro()) return; @@ -70,15 +74,18 @@ export function Copy(context) { context.copyIDs(canCopy); } + function copy() { - keybinding.on(cmd('⌘C'), doCopy); + keybinding.on(uiCmd('⌘C'), doCopy); d3.select(document).call(keybinding); return copy; } + copy.off = function() { d3.select(document).call(keybinding.off); }; + return copy; } diff --git a/modules/behavior/drag.js b/modules/behavior/drag.js index ec9854e6c..dc79f51f2 100644 --- a/modules/behavior/drag.js +++ b/modules/behavior/drag.js @@ -1,9 +1,13 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; -import { prefixCSSProperty, prefixDOMProperty } from '../util/index'; +import { utilRebind } from '../util/rebind'; +import { + utilPrefixCSSProperty, + utilPrefixDOMProperty +} from '../util/index'; + /* - `iD.behavior.drag` is like `d3.behavior.drag`, with the following differences: + `behaviorDrag` is like `d3.behavior.drag`, with the following differences: * The `origin` function is expected to return an [x, y] tuple rather than an {x, y} object. @@ -18,26 +22,27 @@ import { prefixCSSProperty, prefixDOMProperty } from '../util/index'; * Delegation is supported via the `delegate` function. */ -export function drag() { - function d3_eventCancel() { - d3.event.stopPropagation(); - d3.event.preventDefault(); - } - +export function behaviorDrag() { var event = d3.dispatch('start', 'move', 'end'), origin = null, selector = '', filter = null, event_, target, surface; - function eventOf(thiz, argumentz) { - return function(e1) { - e1.target = drag; - d3.customEvent(e1, event.apply, event, [e1.type, thiz, argumentz]); - }; + + function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); } - var d3_event_userSelectProperty = prefixCSSProperty('UserSelect'), + function eventOf(thiz, argumentz) { + return function(e1) { + e1.target = drag; + d3.customEvent(e1, event.apply, event, [e1.type, thiz, argumentz]); + }; + } + + var d3_event_userSelectProperty = utilPrefixCSSProperty('UserSelect'), d3_event_userSelectSuppress = d3_event_userSelectProperty ? function () { var selection = d3.selection(), @@ -54,9 +59,11 @@ export function drag() { }; }; + function mousedown() { target = this; event_ = eventOf(target, arguments); + var eventTarget = d3.event.target, touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, offset, @@ -84,8 +91,8 @@ export function drag() { })[0] : d3.mouse(p); } - function dragmove() { + function dragmove() { var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; @@ -110,6 +117,7 @@ export function drag() { }); } + function dragend() { if (started) { event_({ @@ -125,14 +133,16 @@ export function drag() { selectEnable(); } + function click() { d3_eventCancel(); w.on('click.drag', null); } } + function drag(selection) { - var matchesSelector = prefixDOMProperty('matchesSelector'), + var matchesSelector = utilPrefixDOMProperty('matchesSelector'), delegate = mousedown; if (selector) { @@ -152,29 +162,34 @@ export function drag() { .on('touchstart.drag' + selector, delegate); } + drag.off = function(selection) { selection.on('mousedown.drag' + selector, null) .on('touchstart.drag' + selector, null); }; + drag.delegate = function(_) { if (!arguments.length) return selector; selector = _; return drag; }; + drag.filter = function(_) { if (!arguments.length) return origin; filter = _; return drag; }; + drag.origin = function (_) { if (!arguments.length) return origin; origin = _; return drag; }; + drag.cancel = function() { d3.select(window) .on('mousemove.drag', null) @@ -182,6 +197,7 @@ export function drag() { return drag; }; + drag.target = function() { if (!arguments.length) return target; target = arguments[0]; @@ -189,11 +205,13 @@ export function drag() { return drag; }; + drag.surface = function() { if (!arguments.length) return surface; surface = arguments[0]; return drag; }; - return rebind(drag, event, 'on'); + + return utilRebind(drag, event, 'on'); } diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 1217d850f..da54427b2 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -1,25 +1,32 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { chooseEdge, euclideanDistance } from '../geo/index'; -import { Edit } from './edit'; -import { Hover } from './hover'; -import { Tail } from './tail'; +import { behaviorEdit } from './edit'; +import { behaviorHover } from './hover'; +import { behaviorTail } from './tail'; +import { geoChooseEdge, geoEuclideanDistance } from '../geo/index'; +import { utilRebind } from '../util/rebind'; -export function Draw(context) { + +behaviorDraw.usedTails = {}; +behaviorDraw.disableSpace = false; +behaviorDraw.lastSpace = null; + + +export function behaviorDraw(context) { var dispatch = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish'), keybinding = d3keybinding('draw'), - hover = Hover(context) + hover = behaviorHover(context) .altDisables(true) .on('hover', context.ui().sidebar.hover), - tail = Tail(), - edit = Edit(context), + tail = behaviorTail(), + edit = behaviorEdit(context), closeTolerance = 4, tolerance = 12, mouseLeave = false, lastMouse = null, - cached = Draw; + cached = behaviorDraw; + function datum() { if (d3.event.altKey) return {}; @@ -31,6 +38,7 @@ export function Draw(context) { } } + function mousedown() { function point() { @@ -50,7 +58,7 @@ export function Draw(context) { d3.select(window).on('mouseup.draw', function() { var t2 = +new Date(), p2 = point(), - dist = euclideanDistance(p1, p2); + dist = geoEuclideanDistance(p1, p2); element.on('mousemove.draw', mousemove); d3.select(window).on('mouseup.draw', null); @@ -73,19 +81,23 @@ export function Draw(context) { }, true); } + function mousemove() { lastMouse = d3.event; dispatch.call('move', this, datum()); } + function mouseenter() { mouseLeave = false; } + function mouseleave() { mouseLeave = true; } + function click() { var d = datum(); if (d.type === 'way') { @@ -96,7 +108,7 @@ export function Draw(context) { mouse[1] > pad && mouse[1] < dims[1] - pad; if (trySnap) { - var choice = chooseEdge(context.childNodes(d), context.mouse(), context.projection), + var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection), edge = [d.nodes[choice.index - 1], d.nodes[choice.index]]; dispatch.call('clickWay', this, choice.loc, edge); } else { @@ -111,10 +123,11 @@ export function Draw(context) { } } + function space() { var currSpace = context.mouse(); if (cached.disableSpace && cached.lastSpace) { - var dist = euclideanDistance(cached.lastSpace, currSpace); + var dist = geoEuclideanDistance(cached.lastSpace, currSpace); if (dist > tolerance) { cached.disableSpace = false; } @@ -135,21 +148,25 @@ export function Draw(context) { click(); } + function backspace() { d3.event.preventDefault(); dispatch.call('undo'); } + function del() { d3.event.preventDefault(); dispatch.call('cancel'); } + function ret() { d3.event.preventDefault(); dispatch.call('finish'); } + function draw(selection) { context.install(hover); context.install(edit); @@ -178,6 +195,7 @@ export function Draw(context) { return draw; } + draw.off = function(selection) { context.ui().sidebar.hover.cancel(); context.uninstall(hover); @@ -202,14 +220,12 @@ export function Draw(context) { .call(keybinding.off); }; + draw.tail = function(_) { tail.text(_); return draw; }; - return rebind(draw, dispatch, 'on'); -} -Draw.usedTails = {}; -Draw.disableSpace = false; -Draw.lastSpace = null; + return utilRebind(draw, dispatch, 'on'); +} diff --git a/modules/behavior/draw_way.js b/modules/behavior/draw_way.js index f07d6f388..011613d5f 100644 --- a/modules/behavior/draw_way.js +++ b/modules/behavior/draw_way.js @@ -1,39 +1,69 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { AddEntity, AddMidpoint, AddVertex, MoveNode } from '../actions/index'; -import { Browse, Select } from '../modes/index'; -import { Node, Way } from '../core/index'; -import { chooseEdge, edgeEqual } from '../geo/index'; -import { Draw } from './draw'; -import { entitySelector, functor } from '../util/index'; -export function DrawWay(context, wayId, index, mode, baseGraph) { +import { + actionAddEntity, + actionAddMidpoint, + actionAddVertex, + actionMoveNode +} from '../actions/index'; + +import { + modeBrowse, + modeSelect +} from '../modes/index'; + +import { + coreNode, + coreWay +} from '../core/index'; + +import { + geoChooseEdge, + geoEdgeEqual +} from '../geo/index'; + +import { + behaviorDraw +} from './draw'; + +import { + utilEntitySelector, + utilFunctor +} from '../util/index'; + + +export function behaviorDrawWay(context, wayId, index, mode, baseGraph) { + var way = context.entity(wayId), isArea = context.geometry(wayId) === 'area', finished = false, annotation = t((way.isDegenerate() ? 'operations.start.annotation.' : 'operations.continue.annotation.') + context.geometry(wayId)), - draw = Draw(context); + draw = behaviorDraw(context); var startIndex = typeof index === 'undefined' ? way.nodes.length - 1 : 0, - start = Node({loc: context.graph().entity(way.nodes[startIndex]).loc}), - end = Node({loc: context.map().mouseCoordinates()}), - segment = Way({ + start = coreNode({loc: context.graph().entity(way.nodes[startIndex]).loc}), + end = coreNode({loc: context.map().mouseCoordinates()}), + segment = coreWay({ nodes: typeof index === 'undefined' ? [start.id, end.id] : [end.id, start.id], tags: _.clone(way.tags) }); - var f = context[way.isDegenerate() ? 'replace' : 'perform']; + var fn = context[way.isDegenerate() ? 'replace' : 'perform']; if (isArea) { - f(AddEntity(end), - AddVertex(wayId, end.id, index)); + fn(actionAddEntity(end), + actionAddVertex(wayId, end.id, index) + ); } else { - f(AddEntity(start), - AddEntity(end), - AddEntity(segment)); + fn(actionAddEntity(start), + actionAddEntity(end), + actionAddEntity(segment) + ); } + function move(datum) { var loc; @@ -48,7 +78,7 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { mouse[1] > pad && mouse[1] < dims[1] - pad; if (trySnap) { - loc = chooseEdge(context.childNodes(datum), context.mouse(), context.projection).loc; + loc = geoChooseEdge(context.childNodes(datum), context.mouse(), context.projection).loc; } } @@ -56,20 +86,23 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { loc = context.map().mouseCoordinates(); } - context.replace(MoveNode(end.id, loc)); + context.replace(actionMoveNode(end.id, loc)); } + function undone() { finished = true; - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + function setActiveElements() { var active = isArea ? [wayId, end.id] : [segment.id, start.id, end.id]; - context.surface().selectAll(entitySelector(active)) + context.surface().selectAll(utilEntitySelector(active)) .classed('active', true); } + var drawWay = function(surface) { draw.on('move', move) .on('click', drawWay.add) @@ -91,6 +124,7 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { .on('undone.draw', undone); }; + drawWay.off = function(surface) { if (!finished) context.pop(); @@ -106,6 +140,7 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { .on('undone.draw', null); }; + function ReplaceTemporaryNode(newNode) { return function(graph) { if (isArea) { @@ -123,24 +158,26 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { }; } + // Accept the current position of the temporary node and continue drawing. drawWay.add = function(loc) { - // prevent duplicate nodes var last = context.hasEntity(way.nodes[way.nodes.length - (isArea ? 2 : 1)]); if (last && last.loc[0] === loc[0] && last.loc[1] === loc[1]) return; - var newNode = Node({loc: loc}); + var newNode = coreNode({loc: loc}); context.replace( - AddEntity(newNode), + actionAddEntity(newNode), ReplaceTemporaryNode(newNode), - annotation); + annotation + ); finished = true; context.enter(mode); }; + // Connect the way to an existing way. drawWay.addWay = function(loc, edge) { var previousEdge = startIndex ? @@ -148,34 +185,37 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { [way.nodes[0], way.nodes[1]]; // Avoid creating duplicate segments - if (!isArea && edgeEqual(edge, previousEdge)) + if (!isArea && geoEdgeEqual(edge, previousEdge)) return; - var newNode = Node({ loc: loc }); + var newNode = coreNode({ loc: loc }); context.perform( - AddMidpoint({ loc: loc, edge: edge}, newNode), + actionAddMidpoint({ loc: loc, edge: edge}, newNode), ReplaceTemporaryNode(newNode), - annotation); + annotation + ); finished = true; context.enter(mode); }; + // Connect the way to an existing node and continue drawing. drawWay.addNode = function(node) { - // Avoid creating duplicate segments if (way.areAdjacent(node.id, way.nodes[way.nodes.length - 1])) return; context.perform( ReplaceTemporaryNode(node), - annotation); + annotation + ); finished = true; context.enter(mode); }; + // Finish the draw operation, removing the temporary node. If the way has enough // nodes to be valid, it's selected. Otherwise, return to browse mode. drawWay.finish = function() { @@ -188,18 +228,18 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { if (context.hasEntity(wayId)) { context.enter( - Select(context, [wayId]) - .suppressMenu(true) - .newFeature(true)); + modeSelect(context, [wayId]).suppressMenu(true).newFeature(true) + ); } else { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } }; + // Cancel the draw operation and return to browse, deleting everything drawn. drawWay.cancel = function() { context.perform( - functor(baseGraph), + utilFunctor(baseGraph), t('operations.cancel_draw.annotation')); window.setTimeout(function() { @@ -207,13 +247,15 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { }, 1000); finished = true; - context.enter(Browse(context)); + context.enter(modeBrowse(context)); }; + drawWay.tail = function(text) { draw.tail(text); return drawWay; }; + return drawWay; } diff --git a/modules/behavior/edit.js b/modules/behavior/edit.js index 33e61b1aa..d4ed0f6b9 100644 --- a/modules/behavior/edit.js +++ b/modules/behavior/edit.js @@ -1,13 +1,16 @@ -export function Edit(context) { +export function behaviorEdit(context) { + function edit() { context.map() .minzoom(context.minEditableZoom()); } + edit.off = function() { context.map() .minzoom(0); }; + return edit; } diff --git a/modules/behavior/hash.js b/modules/behavior/hash.js index c696a6787..43d4b5270 100644 --- a/modules/behavior/hash.js +++ b/modules/behavior/hash.js @@ -1,13 +1,15 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { qsString, stringQs } from '../util/index'; +import { utilQsString, utilStringQs } from '../util/index'; -export function Hash(context) { + +export function behaviorHash(context) { var s0 = null, // cached location.hash lat = 90 - 1e-8; // allowable latitude range + var parser = function(map, s) { - var q = stringQs(s); + var q = utilStringQs(s); var args = (q.map || '').split('/').map(Number); if (args.length < 3 || args.some(isNaN)) { return true; // replace bogus hash @@ -17,12 +19,13 @@ export function Hash(context) { } }; + var formatter = function(map) { var mode = context.mode(), center = map.center(), zoom = map.zoom(), precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)), - q = _.omit(stringQs(location.hash.substring(1)), 'comment'), + q = _.omit(utilStringQs(location.hash.substring(1)), 'comment'), newParams = {}; if (mode && mode.id === 'browse') { @@ -40,17 +43,20 @@ export function Hash(context) { '/' + center[0].toFixed(precision) + '/' + center[1].toFixed(precision); - return '#' + qsString(_.assign(q, newParams), true); + return '#' + utilQsString(_.assign(q, newParams), true); }; + function update() { if (context.inIntro()) return; var s1 = formatter(context.map()); if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! } + var throttledUpdate = _.throttle(update, 500); + function hashchange() { if (location.hash === s0) return; // ignore spurious hashchange events if (parser(context.map(), (s0 = location.hash).substring(1))) { @@ -58,6 +64,7 @@ export function Hash(context) { } } + function hash() { context.map() .on('move.hash', throttledUpdate); @@ -69,7 +76,7 @@ export function Hash(context) { .on('hashchange.hash', hashchange); if (location.hash) { - var q = stringQs(location.hash.substring(1)); + var q = utilStringQs(location.hash.substring(1)); if (q.id) context.zoomToEntity(q.id.split(',')[0], !q.map); if (q.comment) context.storage('comment', q.comment); hashchange(); @@ -77,6 +84,7 @@ export function Hash(context) { } } + hash.off = function() { throttledUpdate.cancel(); @@ -92,5 +100,6 @@ export function Hash(context) { location.hash = ''; }; + return hash; } diff --git a/modules/behavior/hover.js b/modules/behavior/hover.js index a28ebffd8..5f4ec963d 100644 --- a/modules/behavior/hover.js +++ b/modules/behavior/hover.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { Entity } from '../core/index'; +import { coreEntity } from '../core/index'; +import { utilRebind } from '../util/rebind'; + /* The hover behavior adds the `.hover` class on mouseover to all elements to which @@ -12,13 +13,14 @@ import { Entity } from '../core/index'; Only one of these elements can have the :hover pseudo-class, but all of them will have the .hover class. */ -export function Hover() { +export function behaviorHover() { var dispatch = d3.dispatch('hover'), selection = d3.select(null), buttonDown, altDisables, target; + function keydown() { if (altDisables && d3.event.keyCode === d3keybinding.modifierCodes.alt) { dispatch.call('hover', this, null); @@ -28,6 +30,7 @@ export function Hover() { } } + function keyup() { if (altDisables && d3.event.keyCode === d3keybinding.modifierCodes.alt) { dispatch.call('hover', this, target ? target.id : null); @@ -37,6 +40,7 @@ export function Hover() { } } + var hover = function(__) { selection = __; @@ -50,7 +54,7 @@ export function Hover() { selection.selectAll('.hover-suppressed') .classed('hover-suppressed', false); - if (target instanceof Entity) { + if (target instanceof coreEntity) { var selector = '.' + target.id; if (target.type === 'relation') { @@ -133,5 +137,5 @@ export function Hover() { }; - return rebind(hover, dispatch, 'on'); + return utilRebind(hover, dispatch, 'on'); } diff --git a/modules/behavior/index.js b/modules/behavior/index.js index 6a04e35bc..da78fa23a 100644 --- a/modules/behavior/index.js +++ b/modules/behavior/index.js @@ -1,13 +1,13 @@ -export { AddWay } from './add_way'; -export { Breathe } from './breathe'; -export { Copy } from './copy'; -export { drag } from './drag'; -export { DrawWay } from './draw_way'; -export { Draw } from './draw'; -export { Edit } from './edit'; -export { Hash } from './hash'; -export { Hover } from './hover'; -export { Lasso } from './lasso'; -export { Paste } from './paste'; -export { Select } from './select'; -export { Tail } from './tail'; +export { behaviorAddWay } from './add_way'; +export { behaviorBreathe } from './breathe'; +export { behaviorCopy } from './copy'; +export { behaviorDrag } from './drag'; +export { behaviorDrawWay } from './draw_way'; +export { behaviorDraw } from './draw'; +export { behaviorEdit } from './edit'; +export { behaviorHash } from './hash'; +export { behaviorHover } from './hover'; +export { behaviorLasso } from './lasso'; +export { behaviorPaste } from './paste'; +export { behaviorSelect } from './select'; +export { behaviorTail } from './tail'; diff --git a/modules/behavior/lasso.js b/modules/behavior/lasso.js index 9c9ce10f9..f42be7ccd 100644 --- a/modules/behavior/lasso.js +++ b/modules/behavior/lasso.js @@ -1,14 +1,16 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, pointInPolygon } from '../geo/index'; -import { Select } from '../modes/index'; -import { Lasso as uiLasso } from '../ui/index'; +import { geoExtent, geoPointInPolygon } from '../geo/index'; +import { modeSelect } from '../modes/index'; +import { uiLasso } from '../ui/index'; -export function Lasso(context) { + +export function behaviorLasso(context) { var behavior = function(selection) { var lasso; + function mousedown() { var button = 0; // left if (d3.event.button === button && d3.event.shiftKey === true) { @@ -22,6 +24,7 @@ export function Lasso(context) { } } + function mousemove() { if (!lasso) { lasso = uiLasso(context); @@ -31,26 +34,29 @@ export function Lasso(context) { lasso.p(context.mouse()); } + function normalize(a, b) { return [ [Math.min(a[0], b[0]), Math.min(a[1], b[1])], [Math.max(a[0], b[0]), Math.max(a[1], b[1])]]; } + function lassoed() { if (!lasso) return []; var graph = context.graph(), bounds = lasso.extent().map(context.projection.invert), - extent = Extent(normalize(bounds[0], bounds[1])); + extent = geoExtent(normalize(bounds[0], bounds[1])); return _.map(context.intersects(extent).filter(function(entity) { return entity.type === 'node' && - pointInPolygon(context.projection(entity.loc), lasso.coordinates) && + geoPointInPolygon(context.projection(entity.loc), lasso.coordinates) && !context.features().isHidden(entity, graph, entity.geometry(graph)); }), 'id'); } + function mouseup() { selection .on('mousemove.lasso', null) @@ -62,7 +68,7 @@ export function Lasso(context) { lasso.close(); if (ids.length) { - context.enter(Select(context, ids)); + context.enter(modeSelect(context, ids)); } } @@ -70,9 +76,11 @@ export function Lasso(context) { .on('mousedown.lasso', mousedown); }; + behavior.off = function(selection) { selection.on('mousedown.lasso', null); }; + return behavior; } diff --git a/modules/behavior/paste.js b/modules/behavior/paste.js index 2c426efee..a8898cf22 100644 --- a/modules/behavior/paste.js +++ b/modules/behavior/paste.js @@ -1,12 +1,23 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { ChangeTags, CopyEntities, Move as MoveAction} from '../actions/index'; -import { Extent, pointInPolygon } from '../geo/index'; -import { Move as MoveMode } from '../modes/index'; -import { cmd } from '../ui/index'; -export function Paste(context) { +import { + actionChangeTags, + actionCopyEntities, + actionMove +} from '../actions/index'; + +import { + geoExtent, + geoPointInPolygon +} from '../geo/index'; + +import { modeMove } from '../modes/index'; +import { uiCmd } from '../ui/index'; + + +export function behaviorPaste(context) { var keybinding = d3keybinding('paste'); function omitTag(v, k) { @@ -25,6 +36,7 @@ export function Paste(context) { ); } + function doPaste() { d3.event.preventDefault(); if (context.inIntro()) return; @@ -32,18 +44,18 @@ export function Paste(context) { var baseGraph = context.graph(), mouse = context.mouse(), projection = context.projection, - viewport = Extent(projection.clipExtent()).polygon(); + viewport = geoExtent(projection.clipExtent()).polygon(); - if (!pointInPolygon(mouse, viewport)) return; + if (!geoPointInPolygon(mouse, viewport)) return; - var extent = Extent(), + var extent = geoExtent(), oldIDs = context.copyIDs(), oldGraph = context.copyGraph(), newIDs = []; if (!oldIDs.length) return; - var action = CopyEntities(oldIDs, oldGraph); + var action = actionCopyEntities(oldIDs, oldGraph); context.perform(action); var copies = action.copies(); @@ -53,26 +65,31 @@ export function Paste(context) { extent._extend(oldEntity.extent(oldGraph)); newIDs.push(newEntity.id); - context.perform(ChangeTags(newEntity.id, _.omit(newEntity.tags, omitTag))); + context.perform( + actionChangeTags(newEntity.id, _.omit(newEntity.tags, omitTag)) + ); } // Put pasted objects where mouse pointer is.. var center = projection(extent.center()), delta = [ mouse[0] - center[0], mouse[1] - center[1] ]; - context.perform(MoveAction(newIDs, delta, projection)); - context.enter(MoveMode(context, newIDs, baseGraph)); + context.perform(actionMove(newIDs, delta, projection)); + context.enter(modeMove(context, newIDs, baseGraph)); } + function paste() { - keybinding.on(cmd('⌘V'), doPaste); + keybinding.on(uiCmd('⌘V'), doPaste); d3.select(document).call(keybinding); return paste; } + paste.off = function() { d3.select(document).call(keybinding.off); }; + return paste; } diff --git a/modules/behavior/select.js b/modules/behavior/select.js index b7f23812e..bf3b7f104 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -1,9 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Browse, Select as SelectMode } from '../modes/index'; -import { Entity } from '../core/index'; +import { modeBrowse, modeSelect } from '../modes/index'; +import { coreEntity } from '../core/index'; + + +export function behaviorSelect(context) { -export function Select(context) { function keydown() { if (d3.event && d3.event.shiftKey) { context.surface() @@ -11,6 +13,7 @@ export function Select(context) { } } + function keyup() { if (!d3.event || !d3.event.shiftKey) { context.surface() @@ -18,33 +21,33 @@ export function Select(context) { } } + function click() { var datum = d3.event.target.__data__, lasso = d3.select('#surface .lasso').node(), mode = context.mode(); - if (!(datum instanceof Entity)) { + if (!(datum instanceof coreEntity)) { if (!d3.event.shiftKey && !lasso && mode.id !== 'browse') - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } else if (!d3.event.shiftKey && !lasso) { // Avoid re-entering Select mode with same entity. if (context.selectedIDs().length !== 1 || context.selectedIDs()[0] !== datum.id) { - context.enter(SelectMode(context, [datum.id])); + context.enter(modeSelect(context, [datum.id])); } else { mode.suppressMenu(false).reselect(); } } else if (context.selectedIDs().indexOf(datum.id) >= 0) { var selectedIDs = _.without(context.selectedIDs(), datum.id); - context.enter(selectedIDs.length ? - SelectMode(context, selectedIDs) : - Browse(context)); + context.enter(selectedIDs.length ? modeSelect(context, selectedIDs) : modeBrowse(context)); } else { - context.enter(SelectMode(context, context.selectedIDs().concat([datum.id]))); + context.enter(modeSelect(context, context.selectedIDs().concat([datum.id]))); } } + var behavior = function(selection) { d3.select(window) .on('keydown.select', keydown) @@ -55,6 +58,7 @@ export function Select(context) { keydown(); }; + behavior.off = function(selection) { d3.select(window) .on('keydown.select', null) @@ -65,5 +69,6 @@ export function Select(context) { keyup(); }; + return behavior; } diff --git a/modules/behavior/tail.js b/modules/behavior/tail.js index 630388f4f..9b6b86c37 100644 --- a/modules/behavior/tail.js +++ b/modules/behavior/tail.js @@ -1,44 +1,21 @@ import * as d3 from 'd3'; -import { setTransform } from '../util/index'; -import { getDimensions } from '../util/dimensions'; +import { utilSetTransform } from '../util/index'; +import { utilGetDimensions } from '../util/dimensions'; -export function Tail() { + +export function behaviorTail() { var text, container, xmargin = 25, tooltipSize = [0, 0], selectionSize = [0, 0]; + function tail(selection) { if (!text) return; d3.select(window) - .on('resize.tail', function() { selectionSize = getDimensions(selection); }); - - function show() { - container.style('display', 'block'); - tooltipSize = getDimensions(container); - } - - function mousemove() { - if (container.style('display') === 'none') show(); - var xoffset = ((d3.event.clientX + tooltipSize[0] + xmargin) > selectionSize[0]) ? - -tooltipSize[0] - xmargin : xmargin; - container.classed('left', xoffset > 0); - setTransform(container, d3.event.clientX + xoffset, d3.event.clientY); - } - - function mouseleave() { - if (d3.event.relatedTarget !== container.node()) { - container.style('display', 'none'); - } - } - - function mouseenter() { - if (d3.event.relatedTarget !== container.node()) { - show(); - } - } + .on('resize.tail', function() { selectionSize = utilGetDimensions(selection); }); container = d3.select(document.body) .append('div') @@ -56,10 +33,40 @@ export function Tail() { container .on('mousemove.tail', mousemove); - tooltipSize = getDimensions(container); - selectionSize = getDimensions(selection); + tooltipSize = utilGetDimensions(container); + selectionSize = utilGetDimensions(selection); + + + function show() { + container.style('display', 'block'); + tooltipSize = utilGetDimensions(container); + } + + + function mousemove() { + if (container.style('display') === 'none') show(); + var xoffset = ((d3.event.clientX + tooltipSize[0] + xmargin) > selectionSize[0]) ? + -tooltipSize[0] - xmargin : xmargin; + container.classed('left', xoffset > 0); + utilSetTransform(container, d3.event.clientX + xoffset, d3.event.clientY); + } + + + function mouseleave() { + if (d3.event.relatedTarget !== container.node()) { + container.style('display', 'none'); + } + } + + + function mouseenter() { + if (d3.event.relatedTarget !== container.node()) { + show(); + } + } } + tail.off = function(selection) { if (!text) return; @@ -76,11 +83,13 @@ export function Tail() { .on('resize.tail', null); }; + tail.text = function(_) { if (!arguments.length) return text; text = _; return tail; }; + return tail; } diff --git a/modules/core/connection.js b/modules/core/connection.js index 3b12d74a6..06139eca2 100644 --- a/modules/core/connection.js +++ b/modules/core/connection.js @@ -1,18 +1,19 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { functor } from '../util/index'; +import { utilRebind } from '../util/rebind'; +import { utilFunctor } from '../util/index'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { Detect } from '../util/detect'; -import { Entity } from './entity'; -import { Extent } from '../geo/index'; +import { geoExtent } from '../geo/index'; +import { utilDetect } from '../util/detect'; +import { coreEntity } from './entity'; +import { coreNode } from './node'; +import { coreRelation } from './relation'; +import { coreWay } from './way'; import { JXON } from '../util/jxon'; -import { Node } from './node'; -import { Relation } from './relation'; -import { Way } from './way'; import osmAuth from 'osm-auth'; -export function Connection(useHttps) { + +export function coreConnection(useHttps) { if (typeof useHttps !== 'boolean') { useHttps = window.location.protocol === 'https:'; } @@ -45,6 +46,7 @@ export function Connection(useHttps) { return url + '/changeset/' + changesetId; }; + connection.changesetsURL = function(center, zoom) { var precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); return url + '/history#map=' + @@ -53,14 +55,17 @@ export function Connection(useHttps) { center[0].toFixed(precision); }; + connection.entityURL = function(entity) { return url + '/' + entity.type + '/' + entity.osmId(); }; + connection.userURL = function(username) { return url + '/user/' + username; }; + connection.loadFromURL = function(url, callback) { function done(err, dom) { return callback(err, parse(dom)); @@ -68,9 +73,10 @@ export function Connection(useHttps) { return d3.xml(url).get(done); }; + connection.loadEntity = function(id, callback) { - var type = Entity.id.type(id), - osmID = Entity.id.toOSM(id); + var type = coreEntity.id.type(id), + osmID = coreEntity.id.toOSM(id); connection.loadFromURL( url + '/api/0.6/' + type + '/' + osmID + (type !== 'node' ? '/full' : ''), @@ -79,9 +85,10 @@ export function Connection(useHttps) { }); }; + connection.loadEntityVersion = function(id, version, callback) { - var type = Entity.id.type(id), - osmID = Entity.id.toOSM(id); + var type = coreEntity.id.type(id), + osmID = coreEntity.id.toOSM(id); connection.loadFromURL( url + '/api/0.6/' + type + '/' + osmID + '/' + version, @@ -90,10 +97,11 @@ export function Connection(useHttps) { }); }; + connection.loadMultiple = function(ids, callback) { - _.each(_.groupBy(_.uniq(ids), Entity.id.type), function(v, k) { + _.each(_.groupBy(_.uniq(ids), coreEntity.id.type), function(v, k) { var type = k + 's', - osmIDs = _.map(v, Entity.id.toOSM); + osmIDs = _.map(v, coreEntity.id.toOSM); _.each(_.chunk(osmIDs, 150), function(arr) { connection.loadFromURL( @@ -105,20 +113,24 @@ export function Connection(useHttps) { }); }; + function authenticating() { dispatch.call('authenticating'); } + function authenticated() { dispatch.call('authenticated'); } + function getLoc(attrs) { var lon = attrs.lon && attrs.lon.value, lat = attrs.lat && attrs.lat.value; return [parseFloat(lon), parseFloat(lat)]; } + function getNodes(obj) { var elems = obj.getElementsByTagName(ndStr), nodes = new Array(elems.length); @@ -128,6 +140,7 @@ export function Connection(useHttps) { return nodes; } + function getTags(obj) { var elems = obj.getElementsByTagName(tagStr), tags = {}; @@ -138,6 +151,7 @@ export function Connection(useHttps) { return tags; } + function getMembers(obj) { var elems = obj.getElementsByTagName(memberStr), members = new Array(elems.length); @@ -152,15 +166,17 @@ export function Connection(useHttps) { return members; } + function getVisible(attrs) { return (!attrs.visible || attrs.visible.value !== 'false'); } + var parsers = { node: function nodeData(obj) { var attrs = obj.attributes; - return new Node({ - id: Entity.id.fromOSM(nodeStr, attrs.id.value), + return new coreNode({ + id: coreEntity.id.fromOSM(nodeStr, attrs.id.value), loc: getLoc(attrs), version: attrs.version.value, user: attrs.user && attrs.user.value, @@ -171,8 +187,8 @@ export function Connection(useHttps) { way: function wayData(obj) { var attrs = obj.attributes; - return new Way({ - id: Entity.id.fromOSM(wayStr, attrs.id.value), + return new coreWay({ + id: coreEntity.id.fromOSM(wayStr, attrs.id.value), version: attrs.version.value, user: attrs.user && attrs.user.value, tags: getTags(obj), @@ -183,8 +199,8 @@ export function Connection(useHttps) { relation: function relationData(obj) { var attrs = obj.attributes; - return new Relation({ - id: Entity.id.fromOSM(relationStr, attrs.id.value), + return new coreRelation({ + id: coreEntity.id.fromOSM(relationStr, attrs.id.value), version: attrs.version.value, user: attrs.user && attrs.user.value, tags: getTags(obj), @@ -194,6 +210,7 @@ export function Connection(useHttps) { } }; + function parse(dom) { if (!dom || !dom.childNodes) return; @@ -212,10 +229,12 @@ export function Connection(useHttps) { return entities; } + connection.authenticated = function() { return oauth.authenticated(); }; + // Generate Changeset XML. Returns a string. connection.changesetJXON = function(tags) { return { @@ -231,6 +250,7 @@ export function Connection(useHttps) { }; }; + // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange) // XML. Returns a string. connection.osmChangeJXON = function(changeset_id, changes) { @@ -263,8 +283,9 @@ export function Connection(useHttps) { }; }; + connection.changesetTags = function(version, comment, imageryUsed) { - var detected = Detect(), + var detected = utilDetect(), tags = { created_by: ('iD ' + version).substr(0, 255), imagery_used: imageryUsed.join(';').substr(0, 255), @@ -279,6 +300,7 @@ export function Connection(useHttps) { return tags; }; + connection.putChangeset = function(changes, version, comment, imageryUsed, callback) { oauth.xhr({ method: 'PUT', @@ -302,11 +324,12 @@ export function Connection(useHttps) { method: 'PUT', path: '/api/0.6/changeset/' + changeset_id + '/close', options: { header: { 'Content-Type': 'text/xml' } } - }, functor(true)); + }, utilFunctor(true)); }); }); }; + connection.userDetails = function(callback) { if (userDetails) { callback(undefined, userDetails); @@ -336,6 +359,7 @@ export function Connection(useHttps) { oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done); }; + connection.userChangesets = function(callback) { connection.userDetails(function(err, user) { if (err) return callback(err); @@ -353,6 +377,7 @@ export function Connection(useHttps) { }); }; + connection.status = function(callback) { function done(capabilities) { var apiStatus = capabilities.getElementsByTagName('status'); @@ -363,7 +388,11 @@ export function Connection(useHttps) { .on('error', callback); }; - function abortRequest(i) { i.abort(); } + + function abortRequest(i) { + i.abort(); + } + connection.tileZoom = function(_) { if (!arguments.length) return tileZoom; @@ -371,8 +400,8 @@ export function Connection(useHttps) { return connection; }; - connection.loadTiles = function(projection, dimensions, callback) { + connection.loadTiles = function(projection, dimensions, callback) { if (off) return; var s = projection.scale() * 2 * Math.PI, @@ -433,6 +462,7 @@ export function Connection(useHttps) { }); }; + connection.switch = function(options) { url = options.url; oauth.options(_.extend({ @@ -444,11 +474,13 @@ export function Connection(useHttps) { return connection; }; + connection.toggle = function(_) { off = !_; return connection; }; + connection.flush = function() { userDetails = undefined; _.forEach(inflight, abortRequest); @@ -457,12 +489,14 @@ export function Connection(useHttps) { return connection; }; + connection.loadedTiles = function(_) { if (!arguments.length) return loadedTiles; loadedTiles = _; return connection; }; + connection.logout = function() { userDetails = undefined; oauth.logout(); @@ -470,6 +504,7 @@ export function Connection(useHttps) { return connection; }; + connection.authenticate = function(callback) { userDetails = undefined; function done(err, res) { @@ -479,5 +514,6 @@ export function Connection(useHttps) { return oauth.authenticate(done); }; - return rebind(connection, dispatch, 'on'); + + return utilRebind(connection, dispatch, 'on'); } diff --git a/modules/core/context.js b/modules/core/context.js index 139819db2..3e550ae41 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -1,19 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import { t, addTranslation, setLocale } from '../util/locale'; -import { Background } from '../renderer/background'; -import { Connection } from './connection'; -import { Detect } from '../util/detect'; -import { Features } from '../renderer/features'; -import { History } from './history'; -import { Map } from '../renderer/map'; -import { Select } from '../modes/select'; -import { RawMercator } from '../geo/raw_mercator'; -import { presets as presetsInit } from '../presets/presets'; -import { init as uiInit } from '../ui/init'; -import { locales, en } from '../../data/index'; +import { coreConnection } from './connection'; +import { coreHistory } from './history'; +import { dataLocales, dataEn } from '../../data/index'; +import { geoRawMercator } from '../geo/raw_mercator'; +import { modeSelect } from '../modes/select'; +import { presetInit } from '../presets/init'; +import { rendererBackground } from '../renderer/background'; +import { rendererFeatures } from '../renderer/features'; +import { rendererMap } from '../renderer/map'; import * as services from '../services/index'; +import { uiInit } from '../ui/init'; +import { utilDetect } from '../util/detect'; +import { utilRebind } from '../util/rebind'; + export var areaKeys = {}; @@ -21,13 +22,14 @@ export function setAreaKeys(value) { areaKeys = value; } -export function Context(root) { + +export function coreContext(root) { if (!root.locale) { root.locale = { current: function(_) { this._current = _; } }; } - addTranslation('en', en); + addTranslation('en', dataEn); setLocale('en'); var dispatch = d3.dispatch('enter', 'exit', 'change'), @@ -106,7 +108,7 @@ export function Context(root) { if (!context.hasEntity(id)) return; map.on('drawn.zoomToEntity', null); context.on('enter.zoomToEntity', null); - context.enter(Select(context, [id])); + context.enter(modeSelect(context, [id])); }); context.on('enter.zoomToEntity', function() { @@ -340,7 +342,7 @@ export function Context(root) { }; context.loadLocale = function(cb) { - if (locale && locale !== 'en' && locales.indexOf(locale) !== -1) { + if (locale && locale !== 'en' && dataLocales.indexOf(locale) !== -1) { localePath = localePath || context.asset('locales/' + locale + '.json'); d3.json(localePath, function(err, result) { addTranslation(locale, result); @@ -356,14 +358,14 @@ export function Context(root) { /* Init */ context.version = '2.0.0-alpha.1'; - context.projection = RawMercator(); + context.projection = geoRawMercator(); - locale = Detect().locale; - if (locale && locales.indexOf(locale) === -1) { + locale = utilDetect().locale; + if (locale && dataLocales.indexOf(locale) === -1) { locale = locale.split('-')[0]; } - history = History(context); + history = coreHistory(context); context.graph = history.graph; context.changes = history.changes; context.intersects = history.intersects; @@ -388,13 +390,12 @@ export function Context(root) { ui = uiInit(context); - connection = Connection(); + connection = coreConnection(); - background = Background(context); + background = rendererBackground(context); + features = rendererFeatures(context); - features = Features(context); - - map = Map(context); + map = rendererMap(context); context.mouse = map.mouse; context.extent = map.extent; context.pan = map.pan; @@ -404,7 +405,7 @@ export function Context(root) { context.zoomOutFurther = map.zoomOutFurther; context.redrawEnable = map.redrawEnable; - presets = presetsInit(); + presets = presetInit(); - return rebind(context, dispatch, 'on'); + return utilRebind(context, dispatch, 'on'); } diff --git a/modules/core/difference.js b/modules/core/difference.js index 281ad9c86..cffe3afac 100644 --- a/modules/core/difference.js +++ b/modules/core/difference.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import _ from 'lodash'; + /* iD.Difference represents the difference between two graphs. It knows how to calculate the set of entities that were @@ -9,13 +10,17 @@ import _ from 'lodash'; of entities that will require a redraw, taking into account child and parent relationships. */ -export function Difference(base, head) { - var changes = {}, length = 0; +export function coreDifference(base, head) { + var changes = {}, + difference = {}, + length = 0; + function changed(h, b) { return h !== b && !_.isEqual(_.omit(h, 'v'), _.omit(b, 'v')); } + _.each(head.entities, function(h, id) { var b = base.entities[id]; if (changed(h, b)) { @@ -24,6 +29,7 @@ export function Difference(base, head) { } }); + _.each(base.entities, function(b, id) { var h = head.entities[id]; if (!changes[id] && changed(h, b)) { @@ -32,6 +38,7 @@ export function Difference(base, head) { } }); + function addParents(parents, result) { for (var i = 0; i < parents.length; i++) { var parent = parents[i]; @@ -44,16 +51,17 @@ export function Difference(base, head) { } } - var difference = {}; difference.length = function() { return length; }; + difference.changes = function() { return changes; }; + difference.extantIDs = function() { var result = []; _.each(changes, function(change, id) { @@ -62,6 +70,7 @@ export function Difference(base, head) { return result; }; + difference.modified = function() { var result = []; _.each(changes, function(change) { @@ -70,6 +79,7 @@ export function Difference(base, head) { return result; }; + difference.created = function() { var result = []; _.each(changes, function(change) { @@ -78,6 +88,7 @@ export function Difference(base, head) { return result; }; + difference.deleted = function() { var result = []; _.each(changes, function(change) { @@ -86,6 +97,7 @@ export function Difference(base, head) { return result; }; + difference.summary = function() { var relevant = {}; @@ -135,6 +147,7 @@ export function Difference(base, head) { return d3.values(relevant); }; + difference.complete = function(extent) { var result = {}, id, change; @@ -175,5 +188,6 @@ export function Difference(base, head) { return result; }; + return difference; } diff --git a/modules/core/entity.js b/modules/core/entity.js index 5fc2253d4..b4452659e 100644 --- a/modules/core/entity.js +++ b/modules/core/entity.js @@ -1,49 +1,61 @@ import _ from 'lodash'; import { debug } from '../index'; -import { interestingTag } from './tags'; -import { deprecated as deprecatedData } from '../../data/index'; +import { coreInterestingTag } from './tags'; +import { dataDeprecated } from '../../data/index'; -export function Entity(attrs) { + +export function coreEntity(attrs) { // For prototypal inheritance. - if (this instanceof Entity) return; + if (this instanceof coreEntity) return; // Create the appropriate subtype. if (attrs && attrs.type) { - return Entity[attrs.type].apply(this, arguments); + return coreEntity[attrs.type].apply(this, arguments); } else if (attrs && attrs.id) { - return Entity[Entity.id.type(attrs.id)].apply(this, arguments); + return coreEntity[coreEntity.id.type(attrs.id)].apply(this, arguments); } // Initialize a generic Entity (used only in tests). - return (new Entity()).initialize(arguments); + return (new coreEntity()).initialize(arguments); } -Entity.id = function(type) { - return Entity.id.fromOSM(type, Entity.id.next[type]--); + +coreEntity.id = function(type) { + return coreEntity.id.fromOSM(type, coreEntity.id.next[type]--); }; -Entity.id.next = {node: -1, way: -1, relation: -1}; -Entity.id.fromOSM = function(type, id) { +coreEntity.id.next = { + node: -1, way: -1, relation: -1 +}; + + +coreEntity.id.fromOSM = function(type, id) { return type[0] + id; }; -Entity.id.toOSM = function(id) { + +coreEntity.id.toOSM = function(id) { return id.slice(1); }; -Entity.id.type = function(id) { - return {'n': 'node', 'w': 'way', 'r': 'relation'}[id[0]]; + +coreEntity.id.type = function(id) { + return { 'n': 'node', 'w': 'way', 'r': 'relation' }[id[0]]; }; + // A function suitable for use as the second argument to d3.selection#data(). -Entity.key = function(entity) { +coreEntity.key = function(entity) { return entity.id + 'v' + (entity.v || 0); }; -Entity.prototype = { + +coreEntity.prototype = { + tags: {}, + initialize: function(sources) { for (var i = 0; i < sources.length; ++i) { var source = sources[i]; @@ -59,7 +71,7 @@ Entity.prototype = { } if (!this.id && this.type) { - this.id = Entity.id(this.type); + this.id = coreEntity.id(this.type); } if (!this.hasOwnProperty('visible')) { this.visible = true; @@ -77,28 +89,33 @@ Entity.prototype = { return this; }, + copy: function(resolver, copies) { if (copies[this.id]) return copies[this.id]; - var copy = Entity(this, {id: undefined, user: undefined, version: undefined}); + var copy = coreEntity(this, {id: undefined, user: undefined, version: undefined}); copies[this.id] = copy; return copy; }, + osmId: function() { - return Entity.id.toOSM(this.id); + return coreEntity.id.toOSM(this.id); }, + isNew: function() { return this.osmId() < 0; }, + update: function(attrs) { - return Entity(this, attrs, {v: 1 + (this.v || 0)}); + return coreEntity(this, attrs, {v: 1 + (this.v || 0)}); }, + mergeTags: function(tags) { var merged = _.clone(this.tags), changed = false; for (var k in tags) { @@ -115,28 +132,33 @@ Entity.prototype = { return changed ? this.update({tags: merged}) : this; }, + intersects: function(extent, resolver) { return this.extent(resolver).intersects(extent); }, + isUsed: function(resolver) { return _.without(Object.keys(this.tags), 'area').length > 0 || resolver.parentRelations(this).length > 0; }, + hasInterestingTags: function() { - return _.keys(this.tags).some(interestingTag); + return _.keys(this.tags).some(coreInterestingTag); }, + isHighwayIntersection: function() { return false; }, + deprecatedTags: function() { var tags = _.toPairs(this.tags); var deprecated = {}; - deprecatedData.forEach(function(d) { + dataDeprecated.forEach(function(d) { var match = _.toPairs(d.old)[0]; tags.forEach(function(t) { if (t[0] === match[0] && diff --git a/modules/core/graph.js b/modules/core/graph.js index 2b7bb9224..ebbcf8b57 100644 --- a/modules/core/graph.js +++ b/modules/core/graph.js @@ -1,11 +1,12 @@ import _ from 'lodash'; -import { getPrototypeOf } from '../util/index'; +import { utilGetPrototypeOf } from '../util/index'; import { debug } from '../index'; -export function Graph(other, mutable) { - if (!(this instanceof Graph)) return new Graph(other, mutable); - if (other instanceof Graph) { +export function coreGraph(other, mutable) { + if (!(this instanceof coreGraph)) return new coreGraph(other, mutable); + + if (other instanceof coreGraph) { var base = other.base(); this.entities = _.assign(Object.create(base.entities), other.entities); this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); @@ -23,11 +24,14 @@ export function Graph(other, mutable) { this.frozen = !mutable; } -Graph.prototype = { + +coreGraph.prototype = { + hasEntity: function(id) { return this.entities[id]; }, + entity: function(id) { var entity = this.entities[id]; if (!entity) { @@ -36,6 +40,7 @@ Graph.prototype = { return entity; }, + transient: function(entity, key, fn) { var id = entity.id, transients = this.transients[id] || @@ -50,6 +55,7 @@ Graph.prototype = { return transients[key]; }, + parentWays: function(entity) { var parents = this._parentWays[entity.id], result = []; @@ -62,16 +68,19 @@ Graph.prototype = { return result; }, + isPoi: function(entity) { var parentWays = this._parentWays[entity.id]; return !parentWays || parentWays.length === 0; }, + isShared: function(entity) { var parentWays = this._parentWays[entity.id]; return parentWays && parentWays.length > 1; }, + parentRelations: function(entity) { var parents = this._parentRels[entity.id], result = []; @@ -84,6 +93,7 @@ Graph.prototype = { return result; }, + childNodes: function(entity) { if (this._childNodes[entity.id]) return this._childNodes[entity.id]; if (!entity.nodes) return []; @@ -99,14 +109,16 @@ Graph.prototype = { return this._childNodes[entity.id]; }, + base: function() { return { - 'entities': getPrototypeOf(this.entities), - 'parentWays': getPrototypeOf(this._parentWays), - 'parentRels': getPrototypeOf(this._parentRels) + 'entities': utilGetPrototypeOf(this.entities), + 'parentWays': utilGetPrototypeOf(this._parentWays), + 'parentRels': utilGetPrototypeOf(this._parentRels) }; }, + // Unlike other graph methods, rebase mutates in place. This is because it // is used only during the history operation that merges newly downloaded // data into each state. To external consumers, it should appear as if the @@ -144,6 +156,7 @@ Graph.prototype = { } }, + _updateRebased: function() { var base = this.base(), i, k, child, id, keys; @@ -180,6 +193,7 @@ Graph.prototype = { // ways are always downloaded with their child nodes. }, + // Updates calculated properties (parentWays, parentRels) for the specified change _updateCalculated: function(oldentity, entity, parentWays, parentRels) { @@ -236,6 +250,7 @@ Graph.prototype = { } }, + replace: function(entity) { if (this.entities[entity.id] === entity) return this; @@ -246,6 +261,7 @@ Graph.prototype = { }); }, + remove: function(entity) { return this.update(function() { this._updateCalculated(entity, undefined); @@ -253,6 +269,7 @@ Graph.prototype = { }); }, + revert: function(id) { var baseEntity = this.base().entities[id], headEntity = this.entities[id]; @@ -266,8 +283,9 @@ Graph.prototype = { }); }, + update: function() { - var graph = this.frozen ? Graph(this, true) : this; + var graph = this.frozen ? coreGraph(this, true) : this; for (var i = 0; i < arguments.length; i++) { arguments[i].call(graph, graph); @@ -278,6 +296,7 @@ Graph.prototype = { return graph; }, + // Obliterates any existing entities load: function(entities) { var base = this.base(); diff --git a/modules/core/history.js b/modules/core/history.js index 35bc48d50..21c6c9e83 100644 --- a/modules/core/history.js +++ b/modules/core/history.js @@ -1,19 +1,21 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import * as Validations from '../validations/index'; -import { Difference } from './difference'; -import { Entity } from './entity'; -import { Graph } from './graph'; -import { Loading } from '../ui/index'; -import { SessionMutex } from '../util/index'; -import { Tree } from './tree'; +import { coreDifference } from './difference'; +import { coreEntity } from './entity'; +import { coreGraph } from './graph'; +import { coreTree } from './tree'; +import { uiLoading } from '../ui/index'; +import { utilSessionMutex } from '../util/index'; +import { utilRebind } from '../util/rebind'; -export function History(context) { + +export function coreHistory(context) { var stack, index, tree, imageryUsed = ['Bing'], dispatch = d3.dispatch('change', 'undone', 'redone'), - lock = SessionMutex('lock'); + lock = utilSessionMutex('lock'); + function perform(actions) { actions = Array.prototype.slice.call(actions); @@ -36,26 +38,32 @@ export function History(context) { }; } + function change(previous) { - var difference = Difference(previous, history.graph()); + var difference = coreDifference(previous, history.graph()); dispatch.call('change', this, difference); return difference; } + // iD uses namespaced keys so multiple installations do not conflict function getKey(n) { return 'iD_' + window.location.origin + '_' + n; } + var history = { + graph: function() { return stack[index].graph; }, + base: function() { return stack[0].graph; }, + merge: function(entities, extent) { stack[0].graph.rebase(entities, _.map(stack, 'graph'), false); tree.rebase(entities, false); @@ -63,6 +71,7 @@ export function History(context) { dispatch.call('change', this, undefined, extent); }, + perform: function() { var previous = stack[index].graph; @@ -73,6 +82,7 @@ export function History(context) { return change(previous); }, + replace: function() { var previous = stack[index].graph; @@ -82,6 +92,7 @@ export function History(context) { return change(previous); }, + pop: function() { var previous = stack[index].graph; @@ -92,6 +103,7 @@ export function History(context) { } }, + // Same as calling pop and then perform overwrite: function() { var previous = stack[index].graph; @@ -107,6 +119,7 @@ export function History(context) { return change(previous); }, + undo: function() { var previous = stack[index].graph; @@ -120,6 +133,7 @@ export function History(context) { return change(previous); }, + redo: function() { var previous = stack[index].graph; @@ -132,6 +146,7 @@ export function History(context) { return change(previous); }, + undoAnnotation: function() { var i = index; while (i >= 0) { @@ -140,6 +155,7 @@ export function History(context) { } }, + redoAnnotation: function() { var i = index + 1; while (i <= stack.length - 1) { @@ -148,16 +164,19 @@ export function History(context) { } }, + intersects: function(extent) { return tree.intersects(extent, stack[index].graph); }, + difference: function() { var base = stack[0].graph, head = stack[index].graph; - return Difference(base, head); + return coreDifference(base, head); }, + changes: function(action) { var base = stack[0].graph, head = stack[index].graph; @@ -166,7 +185,7 @@ export function History(context) { head = action(head); } - var difference = Difference(base, head); + var difference = coreDifference(base, head); return { modified: difference.modified(), @@ -175,6 +194,7 @@ export function History(context) { }; }, + validate: function(changes) { return _(Validations) .map(function(fn) { return fn()(changes, stack[index].graph); }) @@ -182,10 +202,12 @@ export function History(context) { .value(); }, + hasChanges: function() { return this.difference().length() > 0; }, + imageryUsed: function(sources) { if (sources) { imageryUsed = sources; @@ -200,14 +222,16 @@ export function History(context) { } }, + reset: function() { - stack = [{graph: Graph()}]; + stack = [{graph: coreGraph()}]; index = 0; - tree = Tree(stack[0].graph); + tree = coreTree(stack[0].graph); dispatch.call('change'); return history; }, + toJSON: function() { if (!this.hasChanges()) return; @@ -220,7 +244,7 @@ export function History(context) { _.forEach(i.graph.entities, function(entity, id) { if (entity) { - var key = Entity.key(entity); + var key = coreEntity.key(entity); allEntities[key] = entity; modified.push(key); } else { @@ -255,23 +279,24 @@ export function History(context) { entities: _.values(allEntities), baseEntities: _.values(baseEntities), stack: s, - nextIDs: Entity.id.next, + nextIDs: coreEntity.id.next, index: index }); }, + fromJSON: function(json, loadChildNodes) { var h = JSON.parse(json), loadComplete = true; - Entity.id.next = h.nextIDs; + coreEntity.id.next = h.nextIDs; index = h.index; if (h.version === 2 || h.version === 3) { var allEntities = {}; h.entities.forEach(function(entity) { - allEntities[Entity.key(entity)] = Entity(entity); + allEntities[coreEntity.key(entity)] = Entity(entity); }); if (h.version === 3) { @@ -297,7 +322,7 @@ export function History(context) { loadComplete = false; context.redrawEnable(false); - var loading = Loading(context).blocking(true); + var loading = uiLoading(context).blocking(true); context.container().call(loading); var childNodesLoaded = function(err, result) { @@ -345,7 +370,7 @@ export function History(context) { } return { - graph: Graph(stack[0].graph).load(entities), + graph: coreGraph(stack[0].graph).load(entities), annotation: d.annotation, imageryUsed: d.imageryUsed }; @@ -360,7 +385,7 @@ export function History(context) { entities[i] = entity === 'undefined' ? undefined : Entity(entity); } - d.graph = Graph(stack[0].graph).load(entities); + d.graph = coreGraph(stack[0].graph).load(entities); return d; }); } @@ -372,31 +397,37 @@ export function History(context) { return history; }, + save: function() { if (lock.locked()) context.storage(getKey('saved_history'), history.toJSON() || null); return history; }, + clearSaved: function() { context.debouncedSave.cancel(); if (lock.locked()) context.storage(getKey('saved_history'), null); return history; }, + lock: function() { return lock.lock(); }, + unlock: function() { lock.unlock(); }, + // is iD not open in another window and it detects that // there's a history stored in localStorage that's recoverable? restorableChanges: function() { return lock.locked() && !!context.storage(getKey('saved_history')); }, + // load history from a version stored in localStorage restore: function() { if (!lock.locked()) return; @@ -405,11 +436,13 @@ export function History(context) { if (json) history.fromJSON(json, true); }, + _getKey: getKey }; + history.reset(); - return rebind(history, dispatch, 'on'); + return utilRebind(history, dispatch, 'on'); } diff --git a/modules/core/index.js b/modules/core/index.js index 1ba369b11..47e3175c3 100644 --- a/modules/core/index.js +++ b/modules/core/index.js @@ -1,11 +1,11 @@ -export { Connection } from './connection'; -export { Context } from './context'; -export { Difference } from './difference'; -export { Entity } from './entity'; -export { Graph } from './graph'; -export { History } from './history'; -export { Node } from './node'; -export { Relation } from './relation'; -export { oneWayTags, pavedTags, interestingTag } from './tags'; -export { Tree } from './tree'; -export { Way } from './way'; +export { coreConnection } from './connection'; +export { coreContext } from './context'; +export { coreDifference } from './difference'; +export { coreEntity } from './entity'; +export { coreGraph } from './graph'; +export { coreHistory } from './history'; +export { coreNode } from './node'; +export { coreRelation } from './relation'; +export { coreOneWayTags, corePavedTags, coreInterestingTag } from './tags'; +export { coreTree } from './tree'; +export { coreWay } from './way'; diff --git a/modules/core/node.js b/modules/core/node.js index 71356bb1f..0d5433e9d 100644 --- a/modules/core/node.js +++ b/modules/core/node.js @@ -1,37 +1,41 @@ import _ from 'lodash'; -// iD.Node = iD.Entity.node; -import { Entity } from './entity'; -import { Extent } from '../geo/index'; +import { coreEntity } from './entity'; +import { geoExtent } from '../geo/index'; -export function Node() { - if (!(this instanceof Node)) { - return (new Node()).initialize(arguments); +export function coreNode() { + if (!(this instanceof coreNode)) { + return (new coreNode()).initialize(arguments); } else if (arguments.length) { this.initialize(arguments); } } -Entity.node = Node; +coreEntity.node = coreNode; -Node.prototype = Object.create(Entity.prototype); +coreNode.prototype = Object.create(coreEntity.prototype); + +_.extend(coreNode.prototype, { -_.extend(Node.prototype, { type: 'node', + extent: function() { - return new Extent(this.loc); + return new geoExtent(this.loc); }, + geometry: function(graph) { return graph.transient(this, 'geometry', function() { return graph.isPoi(this) ? 'point' : 'vertex'; }); }, + move: function(loc) { return this.update({loc: loc}); }, + isIntersection: function(resolver) { return resolver.transient(this, 'isIntersection', function() { return resolver.parentWays(this).filter(function(parent) { @@ -44,6 +48,7 @@ _.extend(Node.prototype, { }); }, + isHighwayIntersection: function(resolver) { return resolver.transient(this, 'isHighwayIntersection', function() { return resolver.parentWays(this).filter(function(parent) { @@ -52,6 +57,7 @@ _.extend(Node.prototype, { }); }, + isOnAddressLine: function(resolver) { return resolver.transient(this, 'isOnAddressLine', function() { return resolver.parentWays(this).filter(function(parent) { @@ -61,6 +67,7 @@ _.extend(Node.prototype, { }); }, + asJXON: function(changeset_id) { var r = { node: { @@ -77,6 +84,7 @@ _.extend(Node.prototype, { return r; }, + asGeoJSON: function() { return { type: 'Point', diff --git a/modules/core/relation.js b/modules/core/relation.js index 972ab8f95..b02e0754e 100644 --- a/modules/core/relation.js +++ b/modules/core/relation.js @@ -1,39 +1,50 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, joinWays, polygonContainsPolygon, polygonIntersectsPolygon } from '../geo/index'; -import { Entity } from './entity'; +import { coreEntity } from './entity'; +import { + geoExtent, + geoJoinWays, + geoPolygonContainsPolygon, + geoPolygonIntersectsPolygon +} from '../geo/index'; -export function Relation() { - if (!(this instanceof Relation)) { - return (new Relation()).initialize(arguments); + +export function coreRelation() { + if (!(this instanceof coreRelation)) { + return (new coreRelation()).initialize(arguments); } else if (arguments.length) { this.initialize(arguments); } } -Entity.relation = Relation; -Relation.prototype = Object.create(Entity.prototype); -Relation.creationOrder = function(a, b) { - var aId = parseInt(Entity.id.toOSM(a.id), 10); - var bId = parseInt(Entity.id.toOSM(b.id), 10); +coreEntity.relation = coreRelation; + +coreRelation.prototype = Object.create(coreEntity.prototype); + + +coreRelation.creationOrder = function(a, b) { + var aId = parseInt(coreEntity.id.toOSM(a.id), 10); + var bId = parseInt(coreEntity.id.toOSM(b.id), 10); if (aId < 0 || bId < 0) return aId - bId; return bId - aId; }; -_.extend(Relation.prototype, { + +_.extend(coreRelation.prototype, { type: 'relation', members: [], + copy: function(resolver, copies) { if (copies[this.id]) return copies[this.id]; - var copy = Entity.prototype.copy.call(this, resolver, copies); + var copy = coreEntity.prototype.copy.call(this, resolver, copies); var members = this.members.map(function(member) { - return _.extend({}, member, {id: resolver.entity(member.id).copy(resolver, copies).id}); + return _.extend({}, member, { id: resolver.entity(member.id).copy(resolver, copies).id }); }); copy = copy.update({members: members}); @@ -42,13 +53,14 @@ _.extend(Relation.prototype, { return copy; }, + extent: function(resolver, memo) { return resolver.transient(this, 'extent', function() { - if (memo && memo[this.id]) return Extent(); + if (memo && memo[this.id]) return geoExtent(); memo = memo || {}; memo[this.id] = true; - var extent = Extent(); + var extent = geoExtent(); for (var i = 0; i < this.members.length; i++) { var member = resolver.hasEntity(this.members[i].id); if (member) { @@ -59,16 +71,19 @@ _.extend(Relation.prototype, { }); }, + geometry: function(graph) { return graph.transient(this, 'geometry', function() { return this.isMultipolygon() ? 'area' : 'relation'; }); }, + isDegenerate: function() { return this.members.length === 0; }, + // Return an array of members, each extended with an 'index' property whose value // is the member index. indexedMembers: function() { @@ -79,6 +94,7 @@ _.extend(Relation.prototype, { return result; }, + // Return the first member with the given role. A copy of the member object // is returned, extended with an 'index' property whose value is the member index. memberByRole: function(role) { @@ -89,6 +105,7 @@ _.extend(Relation.prototype, { } }, + // Return the first member with the given id. A copy of the member object // is returned, extended with an 'index' property whose value is the member index. memberById: function(id) { @@ -99,6 +116,7 @@ _.extend(Relation.prototype, { } }, + // Return the first member with the given id and role. A copy of the member object // is returned, extended with an 'index' property whose value is the member index. memberByIdAndRole: function(id, role) { @@ -109,29 +127,34 @@ _.extend(Relation.prototype, { } }, + addMember: function(member, index) { var members = this.members.slice(); members.splice(index === undefined ? members.length : index, 0, member); return this.update({members: members}); }, + updateMember: function(member, index) { var members = this.members.slice(); members.splice(index, 1, _.extend({}, members[index], member)); return this.update({members: members}); }, + removeMember: function(index) { var members = this.members.slice(); members.splice(index, 1); return this.update({members: members}); }, + removeMembersWithID: function(id) { var members = _.reject(this.members, function(m) { return m.id === id; }); return this.update({members: members}); }, + // Wherever a member appears with id `needle.id`, replace it with a member // with id `replacement.id`, type `replacement.type`, and the original role, // unless a member already exists with that id and role. Return an updated @@ -154,13 +177,20 @@ _.extend(Relation.prototype, { return this.update({members: members}); }, + asJXON: function(changeset_id) { var r = { relation: { '@id': this.osmId(), '@version': this.version || 0, member: _.map(this.members, function(member) { - return { keyAttributes: { type: member.type, role: member.role, ref: Entity.id.toOSM(member.id) } }; + return { + keyAttributes: { + type: member.type, + role: member.role, + ref: coreEntity.id.toOSM(member.id) + } + }; }), tag: _.map(this.tags, function(v, k) { return { keyAttributes: { k: k, v: v } }; @@ -171,6 +201,7 @@ _.extend(Relation.prototype, { return r; }, + asGeoJSON: function(resolver) { return resolver.transient(this, 'GeoJSON', function () { if (this.isMultipolygon()) { @@ -190,16 +221,19 @@ _.extend(Relation.prototype, { }); }, + area: function(resolver) { return resolver.transient(this, 'area', function() { return d3.geoArea(this.asGeoJSON(resolver)); }); }, + isMultipolygon: function() { return this.tags.type === 'multipolygon'; }, + isComplete: function(resolver) { for (var i = 0; i < this.members.length; i++) { if (!resolver.hasEntity(this.members[i].id)) { @@ -209,10 +243,12 @@ _.extend(Relation.prototype, { return true; }, + isRestriction: function() { return !!(this.tags.type && this.tags.type.match(/^restriction:?/)); }, + // Returns an array [A0, ... An], each Ai being an array of node arrays [Nds0, ... Ndsm], // where Nds0 is an outer ring and subsequent Ndsi's (if any i > 0) being inner rings. // @@ -227,8 +263,8 @@ _.extend(Relation.prototype, { var outers = this.members.filter(function(m) { return 'outer' === (m.role || 'outer'); }), inners = this.members.filter(function(m) { return 'inner' === m.role; }); - outers = joinWays(outers, resolver); - inners = joinWays(inners, resolver); + outers = geoJoinWays(outers, resolver); + inners = geoJoinWays(inners, resolver); outers = outers.map(function(outer) { return _.map(outer.nodes, 'loc'); }); inners = inners.map(function(inner) { return _.map(inner.nodes, 'loc'); }); @@ -236,7 +272,7 @@ _.extend(Relation.prototype, { var result = outers.map(function(o) { // Heuristic for detecting counterclockwise winding order. Assumes // that OpenStreetMap polygons are not hemisphere-spanning. - return [d3.geoArea({type: 'Polygon', coordinates: [o]}) > 2 * Math.PI ? o.reverse() : o]; + return [d3.geoArea({ type: 'Polygon', coordinates: [o] }) > 2 * Math.PI ? o.reverse() : o]; }); function findOuter(inner) { @@ -244,13 +280,13 @@ _.extend(Relation.prototype, { for (o = 0; o < outers.length; o++) { outer = outers[o]; - if (polygonContainsPolygon(outer, inner)) + if (geoPolygonContainsPolygon(outer, inner)) return o; } for (o = 0; o < outers.length; o++) { outer = outers[o]; - if (polygonIntersectsPolygon(outer, inner)) + if (geoPolygonIntersectsPolygon(outer, inner)) return o; } } @@ -258,7 +294,7 @@ _.extend(Relation.prototype, { for (var i = 0; i < inners.length; i++) { var inner = inners[i]; - if (d3.geoArea({type: 'Polygon', coordinates: [inner]}) < 2 * Math.PI) { + if (d3.geoArea({ type: 'Polygon', coordinates: [inner] }) < 2 * Math.PI) { inner = inner.reverse(); } diff --git a/modules/core/tags.js b/modules/core/tags.js index c857fe606..9bea007f1 100644 --- a/modules/core/tags.js +++ b/modules/core/tags.js @@ -1,4 +1,4 @@ -export function interestingTag(key) { +export function coreInterestingTag(key) { return key !== 'attribution' && key !== 'created_by' && key !== 'source' && @@ -7,7 +7,8 @@ export function interestingTag(key) { } -export var oneWayTags = { + +export var coreOneWayTags = { 'aerialway': { 'chair_lift': true, 'mixed_lift': true, @@ -39,7 +40,8 @@ export var oneWayTags = { } }; -export var pavedTags = { + +export var corePavedTags = { 'surface': { 'paved': true, 'asphalt': true, diff --git a/modules/core/tree.js b/modules/core/tree.js index 86a7f1f86..24decfdf6 100644 --- a/modules/core/tree.js +++ b/modules/core/tree.js @@ -1,10 +1,13 @@ import _ from 'lodash'; -import { Difference } from './difference'; +import { coreDifference } from './difference'; import rbush from 'rbush'; -export function Tree(head) { + +export function coreTree(head) { var rtree = rbush(), - bboxes = {}; + bboxes = {}, + tree = {}; + function entityBBox(entity) { var bbox = entity.extent(head).bbox(); @@ -13,6 +16,7 @@ export function Tree(head) { return bbox; } + function updateParents(entity, insertions, memo) { head.parentWays(entity).forEach(function(way) { if (bboxes[way.id]) { @@ -33,7 +37,6 @@ export function Tree(head) { }); } - var tree = {}; tree.rebase = function(entities, force) { var insertions = {}; @@ -61,9 +64,10 @@ export function Tree(head) { return tree; }; + tree.intersects = function(extent, graph) { if (graph !== head) { - var diff = Difference(head, graph), + var diff = coreDifference(head, graph), insertions = {}; head = graph; @@ -91,5 +95,6 @@ export function Tree(head) { }); }; + return tree; } diff --git a/modules/core/way.js b/modules/core/way.js index 19631b378..3ae29da12 100644 --- a/modules/core/way.js +++ b/modules/core/way.js @@ -1,45 +1,50 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, cross } from '../geo/index'; -import { Entity } from './entity'; -import { oneWayTags } from './tags'; +import { geoExtent, geoCross } from '../geo/index'; +import { coreEntity } from './entity'; +import { coreOneWayTags } from './tags'; import { areaKeys } from './context'; -export function Way() { - if (!(this instanceof Way)) { - return (new Way()).initialize(arguments); + +export function coreWay() { + if (!(this instanceof coreWay)) { + return (new coreWay()).initialize(arguments); } else if (arguments.length) { this.initialize(arguments); } } -Entity.way = Way; -Way.prototype = Object.create(Entity.prototype); +coreEntity.way = coreWay; -_.extend(Way.prototype, { +coreWay.prototype = Object.create(coreEntity.prototype); + + +_.extend(coreWay.prototype, { type: 'way', nodes: [], + copy: function(resolver, copies) { if (copies[this.id]) return copies[this.id]; - var copy = Entity.prototype.copy.call(this, resolver, copies); + var copy = coreEntity.prototype.copy.call(this, resolver, copies); var nodes = this.nodes.map(function(id) { return resolver.entity(id).copy(resolver, copies).id; }); - copy = copy.update({nodes: nodes}); + copy = copy.update({ nodes: nodes }); copies[this.id] = copy; return copy; }, + extent: function(resolver) { return resolver.transient(this, 'extent', function() { - var extent = Extent(); + var extent = geoExtent(); for (var i = 0; i < this.nodes.length; i++) { var node = resolver.hasEntity(this.nodes[i]); if (node) { @@ -50,23 +55,28 @@ _.extend(Way.prototype, { }); }, + first: function() { return this.nodes[0]; }, + last: function() { return this.nodes[this.nodes.length - 1]; }, + contains: function(node) { return this.nodes.indexOf(node) >= 0; }, + affix: function(node) { if (this.nodes[0] === node) return 'prefix'; if (this.nodes[this.nodes.length - 1] === node) return 'suffix'; }, + layer: function() { // explicit layer tag, clamp between -10, 10.. if (isFinite(this.tags.layer)) { @@ -90,6 +100,7 @@ _.extend(Way.prototype, { return 0; }, + isOneWay: function() { // explicit oneway tag.. if (['yes', '1', '-1'].indexOf(this.tags.oneway) !== -1) { return true; } @@ -97,12 +108,13 @@ _.extend(Way.prototype, { // implied oneway tag.. for (var key in this.tags) { - if (key in oneWayTags && (this.tags[key] in oneWayTags[key])) + if (key in coreOneWayTags && (this.tags[key] in coreOneWayTags[key])) return true; } return false; }, + lanes: function() { function makeLanesArray(metadata) { @@ -186,10 +198,12 @@ _.extend(Way.prototype, { }; }, + isClosed: function() { return this.nodes.length > 0 && this.first() === this.last(); }, + isConvex: function(resolver) { if (!this.isClosed() || this.isDegenerate()) return null; @@ -201,7 +215,7 @@ _.extend(Way.prototype, { var o = coords[(i+1) % coords.length], a = coords[i], b = coords[(i+2) % coords.length], - res = cross(o, a, b); + res = geoCross(o, a, b); curr = (res > 0) ? 1 : (res < 0) ? -1 : 0; if (curr === 0) { @@ -214,6 +228,7 @@ _.extend(Way.prototype, { return true; }, + isArea: function() { if (this.tags.area === 'yes') return true; @@ -227,10 +242,12 @@ _.extend(Way.prototype, { return false; }, + isDegenerate: function() { return _.uniq(this.nodes).length < (this.isArea() ? 3 : 2); }, + areAdjacent: function(n1, n2) { for (var i = 0; i < this.nodes.length; i++) { if (this.nodes[i] === n1) { @@ -241,24 +258,28 @@ _.extend(Way.prototype, { return false; }, + geometry: function(graph) { return graph.transient(this, 'geometry', function() { return this.isArea() ? 'area' : 'line'; }); }, + addNode: function(id, index) { var nodes = this.nodes.slice(); nodes.splice(index === undefined ? nodes.length : index, 0, id); return this.update({nodes: nodes}); }, + updateNode: function(id, index) { var nodes = this.nodes.slice(); nodes.splice(index, 1, id); return this.update({nodes: nodes}); }, + replaceNode: function(needle, replacement) { if (this.nodes.indexOf(needle) < 0) return this; @@ -272,6 +293,7 @@ _.extend(Way.prototype, { return this.update({nodes: nodes}); }, + removeNode: function(id) { var nodes = []; @@ -290,13 +312,14 @@ _.extend(Way.prototype, { return this.update({nodes: nodes}); }, + asJXON: function(changeset_id) { var r = { way: { '@id': this.osmId(), '@version': this.version || 0, nd: _.map(this.nodes, function(id) { - return { keyAttributes: { ref: Entity.id.toOSM(id) } }; + return { keyAttributes: { ref: coreEntity.id.toOSM(id) } }; }), tag: _.map(this.tags, function(v, k) { return { keyAttributes: { k: k, v: v } }; @@ -307,6 +330,7 @@ _.extend(Way.prototype, { return r; }, + asGeoJSON: function(resolver) { return resolver.transient(this, 'GeoJSON', function() { var coordinates = _.map(resolver.childNodes(this), 'loc'); @@ -324,6 +348,7 @@ _.extend(Way.prototype, { }); }, + area: function(resolver) { return resolver.transient(this, 'area', function() { var nodes = resolver.childNodes(this); diff --git a/modules/geo/extent.js b/modules/geo/extent.js index f5b8472ee..523d5362c 100644 --- a/modules/geo/extent.js +++ b/modules/geo/extent.js @@ -1,9 +1,13 @@ import _ from 'lodash'; -import { metersToLat, metersToLon } from './index'; +import { + geoMetersToLat, + geoMetersToLon +} from './index'; -export function Extent(min, max) { - if (!(this instanceof Extent)) return new Extent(min, max); - if (min instanceof Extent) { + +export function geoExtent(min, max) { + if (!(this instanceof geoExtent)) return new geoExtent(min, max); + if (min instanceof geoExtent) { return min; } else if (min && min.length === 2 && min[0].length === 2 && min[1].length === 2) { this[0] = min[0]; @@ -14,9 +18,10 @@ export function Extent(min, max) { } } -Extent.prototype = new Array(2); +geoExtent.prototype = new Array(2); + +_.extend(geoExtent.prototype, { -_.extend(Extent.prototype, { equals: function (obj) { return this[0][0] === obj[0][0] && this[0][1] === obj[0][1] && @@ -24,14 +29,16 @@ _.extend(Extent.prototype, { this[1][1] === obj[1][1]; }, + extend: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof geoExtent)) obj = new geoExtent(obj); return Extent([Math.min(obj[0][0], this[0][0]), Math.min(obj[0][1], this[0][1])], [Math.max(obj[1][0], this[1][0]), Math.max(obj[1][1], this[1][1])]); }, + _extend: function(extent) { this[0][0] = Math.min(extent[0][0], this[0][0]); this[0][1] = Math.min(extent[0][1], this[0][1]); @@ -39,23 +46,28 @@ _.extend(Extent.prototype, { this[1][1] = Math.max(extent[1][1], this[1][1]); }, + area: function() { return Math.abs((this[1][0] - this[0][0]) * (this[1][1] - this[0][1])); }, + center: function() { return [(this[0][0] + this[1][0]) / 2, (this[0][1] + this[1][1]) / 2]; }, + rectangle: function() { return [this[0][0], this[0][1], this[1][0], this[1][1]]; }, + bbox: function() { return { minX: this[0][0], minY: this[0][1], maxX: this[1][0], maxY: this[1][1] }; }, + polygon: function() { return [ [this[0][0], this[0][1]], @@ -66,32 +78,36 @@ _.extend(Extent.prototype, { ]; }, + contains: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof Extent)) obj = new geoExtent(obj); return obj[0][0] >= this[0][0] && obj[0][1] >= this[0][1] && obj[1][0] <= this[1][0] && obj[1][1] <= this[1][1]; }, + intersects: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof Extent)) obj = new geoExtent(obj); return obj[0][0] <= this[1][0] && obj[0][1] <= this[1][1] && obj[1][0] >= this[0][0] && obj[1][1] >= this[0][1]; }, + intersection: function(obj) { - if (!this.intersects(obj)) return new Extent(); + if (!this.intersects(obj)) return new geoExtent(); return new Extent([Math.max(obj[0][0], this[0][0]), Math.max(obj[0][1], this[0][1])], [Math.min(obj[1][0], this[1][0]), Math.min(obj[1][1], this[1][1])]); }, + percentContainedIn: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof Extent)) obj = new geoExtent(obj); var a1 = this.intersection(obj).area(), a2 = this.area(); @@ -102,14 +118,16 @@ _.extend(Extent.prototype, { } }, + padByMeters: function(meters) { - var dLat = metersToLat(meters), - dLon = metersToLon(meters, this.center()[1]); + var dLat = geoMetersToLat(meters), + dLon = geoMetersToLon(meters, this.center()[1]); return Extent( [this[0][0] - dLon, this[0][1] - dLat], [this[1][0] + dLon, this[1][1] + dLat]); }, + toParam: function() { return this.rectangle().join(','); } diff --git a/modules/geo/index.js b/modules/geo/index.js index cadd53749..bde46a325 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -1,69 +1,78 @@ import _ from 'lodash'; -export { Extent } from './extent.js'; +export { geoExtent } from './extent.js'; export { - Intersection, - Turn, - inferRestriction + geoIntersection, + geoTurn, + geoInferRestriction } from './intersection.js'; export { - isSimpleMultipolygonOuterMember, - simpleMultipolygonOuterMember, - joinWays + geoIsSimpleMultipolygonOuterMember, + geoSimpleMultipolygonOuterMember, + geoJoinWays } from './multipolygon.js'; -export { RawMercator } from './raw_mercator.js'; +export { geoRawMercator } from './raw_mercator.js'; -export function roundCoords(c) { + +export function geoRoundCoords(c) { return [Math.floor(c[0]), Math.floor(c[1])]; } -export function interp(p1, p2, t) { + +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 cross(o, a, b) { +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 euclideanDistance(a, b) { +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 latToMeters(dLat) { +export function geoLatToMeters(dLat) { return dLat * 110946.257617; } + // using WGS84 equatorial radius (6378137.0 m) // const = 2 * PI * r / 360 -export function lonToMeters(dLon, atLat) { +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 metersToLat(m) { +export function geoMetersToLat(m) { return m / 110946.257617; } + // using WGS84 equatorial radius (6378137.0 m) // const = 2 * PI * r / 360 -export function metersToLon(m, atLat) { +export function geoMetersToLon(m, atLat) { return Math.abs(atLat) >= 90 ? 0 : m / 111319.490793 / Math.abs(Math.cos(atLat * (Math.PI/180))); } -export function offsetToMeters(offset) { + +export function geoOffsetToMeters(offset) { var equatRadius = 6356752.314245179, polarRadius = 6378137.0, tileSize = 256; @@ -74,7 +83,8 @@ export function offsetToMeters(offset) { ]; } -export function metersToOffset(meters) { + +export function geoMetersToOffset(meters) { var equatRadius = 6356752.314245179, polarRadius = 6378137.0, tileSize = 256; @@ -85,31 +95,35 @@ export function metersToOffset(meters) { ]; } + // Equirectangular approximation of spherical distances on Earth -export function sphericalDistance(a, b) { - var x = lonToMeters(a[0] - b[0], (a[1] + b[1]) / 2), - y = latToMeters(a[1] - b[1]); +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 edgeEqual(a, b) { + +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 angle(a, b, projection) { +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 chooseEdge(nodes, point, projection) { +export function geoChooseEdge(nodes, point, projection) { var dist = euclideanDistance, points = nodes.map(function(n) { return projection(n.loc); }), min = Infinity, @@ -151,11 +165,12 @@ export function chooseEdge(nodes, point, projection) { }; } + // 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 lineIntersection(a, b) { +export function geoLineIntersection(a, b) { function subtractPoints(point1, point2) { return [point1[0] - point2[0], point1[1] - point2[1]]; } @@ -184,19 +199,21 @@ export function lineIntersection(a, b) { return null; } -export function pathIntersections(path1, path2) { + +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 = lineIntersection(a, b); + 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. @@ -206,7 +223,7 @@ export function pathIntersections(path1, path2) { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html // -export function pointInPolygon(point, polygon) { +export function geoPointInPolygon(point, polygon) { var x = point[0], y = point[1], inside = false; @@ -223,19 +240,21 @@ export function pointInPolygon(point, polygon) { return inside; } -export function polygonContainsPolygon(outer, inner) { + +export function geoPolygonContainsPolygon(outer, inner) { return _.every(inner, function(point) { - return pointInPolygon(point, outer); + return geoPointInPolygon(point, outer); }); } -export function polygonIntersectsPolygon(outer, inner, checkSegments) { + +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 (lineIntersection(a, b)) return true; + if (geoLineIntersection(a, b)) return true; } } return false; @@ -243,14 +262,15 @@ export function polygonIntersectsPolygon(outer, inner, checkSegments) { function testPoints(outer, inner) { return _.some(inner, function(point) { - return pointInPolygon(point, outer); + return geoPointInPolygon(point, outer); }); } return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner)); } -export function pathLength(path) { + +export function geoPathLength(path) { var length = 0, dx, dy; for (var i = 0; i < path.length - 1; i++) { diff --git a/modules/geo/intersection.js b/modules/geo/intersection.js index 9ac7f79d7..ce4401009 100644 --- a/modules/geo/intersection.js +++ b/modules/geo/intersection.js @@ -1,14 +1,16 @@ import _ from 'lodash'; -import { Way } from '../core/index'; -import { angle as getAngle } from './index'; +import { coreWay } from '../core/index'; +import { geoAngle } from './index'; -export function Turn(turn) { - if (!(this instanceof Turn)) - return new Turn(turn); + +export function geoTurn(turn) { + if (!(this instanceof geoTurn)) + return new geoTurn(turn); _.extend(this, turn); } -export function Intersection(graph, vertexId) { + +export function geoIntersection(graph, vertexId) { var vertex = graph.entity(vertexId), parentWays = graph.parentWays(vertex), coincident = [], @@ -42,14 +44,14 @@ export function Intersection(graph, vertexId) { var splitIndex, wayA, wayB, indexA, indexB; if (isClosingNode) { splitIndex = Math.ceil(way.nodes.length / 2); // split at midpoint - wayA = Way({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex)}); - wayB = Way({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); + wayA = coreWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex)}); + wayB = coreWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); indexA = 1; indexB = way.nodes.length - 2; } else { splitIndex = _.indexOf(way.nodes, vertex.id, 1); // split at vertexid - wayA = Way({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex + 1)}); - wayB = Way({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); + wayA = coreWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex + 1)}); + wayB = coreWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); indexA = splitIndex - 1; indexB = splitIndex + 1; } @@ -72,12 +74,14 @@ export function Intersection(graph, vertexId) { graph: graph }; + intersection.adjacentNodeId = function(fromWayId) { return _.find(_.keys(highways), function(k) { return highways[k].id === fromWayId; }); }; + intersection.turns = function(fromNodeId) { var start = highways[fromNodeId]; if (!start) @@ -110,9 +114,10 @@ export function Intersection(graph, vertexId) { } }); - return Turn(turn); + return geoTurn(turn); } + var from = { node: fromNodeId, way: start.id.split(/-(a|b)/)[0] @@ -167,7 +172,7 @@ export function Intersection(graph, vertexId) { } -export function inferRestriction(graph, from, via, to, projection) { +export function geoInferRestriction(graph, from, via, to, projection) { var fromWay = graph.entity(from.way), fromNode = graph.entity(from.node), toWay = graph.entity(to.way), @@ -177,8 +182,8 @@ export function inferRestriction(graph, from, via, to, projection) { (fromWay.tags.oneway === '-1' && fromWay.first() === via.node), toOneWay = (toWay.tags.oneway === 'yes' && toWay.first() === via.node) || (toWay.tags.oneway === '-1' && toWay.last() === via.node), - angle = getAngle(viaNode, fromNode, projection) - - getAngle(viaNode, toNode, projection); + angle = geoAngle(viaNode, fromNode, projection) - + geoAngle(viaNode, toNode, projection); angle = angle * 180 / Math.PI; diff --git a/modules/geo/multipolygon.js b/modules/geo/multipolygon.js index 67b8f809c..baf558dc4 100644 --- a/modules/geo/multipolygon.js +++ b/modules/geo/multipolygon.js @@ -1,9 +1,10 @@ import _ from 'lodash'; -import { Reverse } from '../actions/reverse'; +import { actionReverse } from '../actions/reverse'; + // For fixing up rendering of multipolygons with tags on the outer member. // https://github.com/openstreetmap/iD/issues/613 -export function isSimpleMultipolygonOuterMember(entity, graph) { +export function geoIsSimpleMultipolygonOuterMember(entity, graph) { if (entity.type !== 'way') return false; @@ -27,7 +28,8 @@ export function isSimpleMultipolygonOuterMember(entity, graph) { return parent; } -export function simpleMultipolygonOuterMember(entity, graph) { + +export function geoSimpleMultipolygonOuterMember(entity, graph) { if (entity.type !== 'way') return false; @@ -52,6 +54,7 @@ export function simpleMultipolygonOuterMember(entity, graph) { return outerMember && graph.hasEntity(outerMember.id); } + // Join `array` into sequences of connecting ways. // // Segments which share identical start/end nodes will, as much as possible, @@ -72,7 +75,7 @@ export function simpleMultipolygonOuterMember(entity, graph) { // Incomplete members (those for which `graph.hasEntity(element.id)` returns // false) and non-way members are ignored. // -export function joinWays(array, graph) { +export function geoJoinWays(array, graph) { var joined = [], member, current, nodes, first, last, i, how, what; array = array.filter(function(member) { @@ -84,7 +87,7 @@ export function joinWays(array, graph) { } function reverse(member) { - return member.tags ? Reverse(member.id, {reverseOneway: true})(graph).entity(member.id) : member; + return member.tags ? actionReverse(member.id, { reverseOneway: true })(graph).entity(member.id) : member; } while (array.length) { diff --git a/modules/geo/raw_mercator.js b/modules/geo/raw_mercator.js index 2a4a84710..931e53fd4 100644 --- a/modules/geo/raw_mercator.js +++ b/modules/geo/raw_mercator.js @@ -6,28 +6,32 @@ import * as d3 from 'd3'; * Spherical rotation * Resampling */ -export function RawMercator() { +export function geoRawMercator() { var project = d3.geoMercatorRaw, k = 512 / Math.PI, // scale x = 0, y = 0, // translate clipExtent = [[0, 0], [0, 0]]; + function projection(point) { point = project(point[0] * Math.PI / 180, point[1] * Math.PI / 180); return [point[0] * k + x, y - point[1] * k]; } + projection.invert = function(point) { point = project.invert((point[0] - x) / k, (y - point[1]) / k); return point && [point[0] * 180 / Math.PI, point[1] * 180 / Math.PI]; }; + projection.scale = function(_) { if (!arguments.length) return k; k = +_; return projection; }; + projection.translate = function(_) { if (!arguments.length) return [x, y]; x = +_[0]; @@ -35,12 +39,14 @@ export function RawMercator() { return projection; }; + projection.clipExtent = function(_) { if (!arguments.length) return clipExtent; clipExtent = _; return projection; }; + projection.transform = function(_) { if (!arguments.length) return d3.zoomIdentity.translate(x, y).scale(k); x = +_.x; @@ -49,6 +55,7 @@ export function RawMercator() { return projection; }; + projection.stream = d3.geoTransform({ point: function(x, y) { x = projection([x, y]); @@ -56,5 +63,6 @@ export function RawMercator() { } }).stream; + return projection; } diff --git a/modules/index.js b/modules/index.js index 6880c1cee..a7c4c16d5 100644 --- a/modules/index.js +++ b/modules/index.js @@ -1,5 +1,6 @@ import * as actions from './actions/index'; import * as behavior from './behavior/index'; +import * as data from '../data/index.js'; import * as geo from './geo/index'; import * as modes from './modes/index'; import * as operations from './operations/index'; @@ -11,47 +12,46 @@ import * as util from './util/index'; import * as lib from './lib/index'; import * as validations from './validations/index'; -// detect -export { Detect } from './util/detect'; +export { coreConnection as Connection } from './core/connection'; +export { coreContext as Context, setAreaKeys } from './core/context'; +export { coreDifference as Difference } from './core/difference'; +export { coreEntity as Entity } from './core/entity'; +export { coreGraph as Graph } from './core/graph'; +export { coreHistory as History } from './core/history'; +export { coreNode as Node } from './core/node'; +export { coreRelation as Relation } from './core/relation'; +export { coreTree as Tree } from './core/tree'; +export { coreWay as Way } from './core/way'; +export { + coreOneWayTags as oneWayTags, + corePavedTags as pavedTags, + coreInterestingTag as interestingTag +} from './core/tags'; -// core -export { Connection } from './core/connection'; -export { Context, setAreaKeys } from './core/context'; -export { Difference } from './core/difference'; -export { Entity } from './core/entity'; -export { Graph } from './core/graph'; -export { History } from './core/history'; -export { Node } from './core/node'; -export { Relation } from './core/relation'; -export { oneWayTags, pavedTags, interestingTag } from './core/tags'; -export { Tree } from './core/tree'; -export { Way } from './core/way'; +export { rendererBackgroundSource as BackgroundSource } from './renderer/background_source'; +export { rendererBackground as Background } from './renderer/background'; +export { rendererFeatures as Features } from './renderer/features'; +export { rendererMap as Map } from './renderer/map'; +export { rendererTileLayer as TileLayer } from './renderer/tile_layer'; -// renderer -export { BackgroundSource } from './renderer/background_source'; -export { Background } from './renderer/background'; -export { Features } from './renderer/features'; -export { Map } from './renderer/map'; -export { TileLayer } from './renderer/tile_layer'; - -import * as data from '../data/index.js'; +export { utilDetect as Detect } from './util/detect'; export var debug = false; import * as d3 from 'd3'; export { - d3, - data, - actions, - geo, - behavior, - modes, - operations, - presets, - services, - svg, - util, - lib, - ui, - validations + d3, + data, + actions, + geo, + behavior, + modes, + operations, + presets, + services, + svg, + util, + lib, + ui, + validations }; diff --git a/modules/lib/d3.combobox.js b/modules/lib/d3.combobox.js index 109deb942..7b4fe95cb 100644 --- a/modules/lib/d3.combobox.js +++ b/modules/lib/d3.combobox.js @@ -1,5 +1,6 @@ -import { rebind } from '../../modules/util/rebind'; import * as d3 from 'd3'; +import { utilRebind } from '../../modules/util/rebind'; + export function d3combobox() { var event = d3.dispatch('accept'), @@ -288,9 +289,10 @@ export function d3combobox() { return combobox; }; - return rebind(combobox, event, 'on'); + return utilRebind(combobox, event, 'on'); } + d3combobox.off = function(input) { input .on('focus.typeahead', null) diff --git a/modules/modes/add_area.js b/modules/modes/add_area.js index f7f661ba6..67cb02f52 100644 --- a/modules/modes/add_area.js +++ b/modules/modes/add_area.js @@ -1,10 +1,16 @@ import { t } from '../util/locale'; -import { AddEntity, AddMidpoint, AddVertex } from '../actions/index'; -import { Node, Way } from '../core/index'; -import { AddWay } from '../behavior/index'; -import { DrawArea } from './index'; +import { + actionAddEntity, + actionAddMidpoint, + actionAddVertex +} from '../actions/index'; -export function AddArea(context) { +import { coreNode, coreWay } from '../core/index'; +import { behaviorAddWay } from '../behavior/index'; +import { modeDrawArea } from './index'; + + +export function modeAddArea(context) { var mode = { id: 'add-area', button: 'area', @@ -13,61 +19,70 @@ export function AddArea(context) { key: '3' }; - var behavior = AddWay(context) + var behavior = behaviorAddWay(context) .tail(t('modes.add_area.tail')) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode), - defaultTags = {area: 'yes'}; + defaultTags = { area: 'yes' }; + function start(loc) { var graph = context.graph(), - node = Node({loc: loc}), - way = Way({tags: defaultTags}); + node = coreNode({ loc: loc }), + way = coreWay({ tags: defaultTags }); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id), - AddVertex(way.id, node.id)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawArea(context, way.id, graph)); + context.enter(modeDrawArea(context, way.id, graph)); } + function startFromWay(loc, edge) { var graph = context.graph(), - node = Node({loc: loc}), - way = Way({tags: defaultTags}); + node = coreNode({ loc: loc }), + way = coreWay({ tags: defaultTags }); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id), - AddVertex(way.id, node.id), - AddMidpoint({ loc: loc, edge: edge }, node)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddVertex(way.id, node.id), + actionAddMidpoint({ loc: loc, edge: edge }, node) + ); - context.enter(DrawArea(context, way.id, graph)); + context.enter(modeDrawArea(context, way.id, graph)); } + function startFromNode(node) { var graph = context.graph(), - way = Way({tags: defaultTags}); + way = coreWay({ tags: defaultTags }); context.perform( - AddEntity(way), - AddVertex(way.id, node.id), - AddVertex(way.id, node.id)); + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawArea(context, way.id, graph)); + context.enter(modeDrawArea(context, way.id, graph)); } + mode.enter = function() { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + return mode; } diff --git a/modules/modes/add_line.js b/modules/modes/add_line.js index 393242ccf..bfdca0b2f 100644 --- a/modules/modes/add_line.js +++ b/modules/modes/add_line.js @@ -1,10 +1,16 @@ import { t } from '../util/locale'; -import { AddEntity, AddMidpoint, AddVertex } from '../actions/index'; -import { Node, Way } from '../core/index'; -import { AddWay } from '../behavior/index'; -import { DrawLine } from './index'; +import { + actionAddEntity, + actionAddMidpoint, + actionAddVertex +} from '../actions/index'; -export function AddLine(context) { +import { coreNode, coreWay } from '../core/index'; +import { behaviorAddWay } from '../behavior/index'; +import { modeDrawLine } from './index'; + + +export function modeAddLine(context) { var mode = { id: 'add-line', button: 'line', @@ -13,54 +19,62 @@ export function AddLine(context) { key: '2' }; - var behavior = AddWay(context) + var behavior = behaviorAddWay(context) .tail(t('modes.add_line.tail')) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode); + function start(loc) { var baseGraph = context.graph(), - node = Node({loc: loc}), - way = Way(); + node = coreNode({ loc: loc }), + way = coreWay(); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawLine(context, way.id, baseGraph)); + context.enter(modeDrawLine(context, way.id, baseGraph)); } + function startFromWay(loc, edge) { var baseGraph = context.graph(), - node = Node({loc: loc}), - way = Way(); + node = coreNode({ loc: loc }), + way = coreWay(); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id), - AddMidpoint({ loc: loc, edge: edge }, node)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddMidpoint({ loc: loc, edge: edge }, node) + ); - context.enter(DrawLine(context, way.id, baseGraph)); + context.enter(modeDrawLine(context, way.id, baseGraph)); } + function startFromNode(node) { var baseGraph = context.graph(), - way = Way(); + way = coreWay(); context.perform( - AddEntity(way), - AddVertex(way.id, node.id)); + actionAddEntity(way), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawLine(context, way.id, baseGraph)); + context.enter(modeDrawLine(context, way.id, baseGraph)); } + mode.enter = function() { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; diff --git a/modules/modes/add_point.js b/modules/modes/add_point.js index ad488656e..c93d861ae 100644 --- a/modules/modes/add_point.js +++ b/modules/modes/add_point.js @@ -1,10 +1,11 @@ import { t } from '../util/locale'; -import { Browse, Select } from './index'; -import { AddEntity } from '../actions/index'; -import { Draw } from '../behavior/index'; -import { Node } from '../core/index'; +import { modeBrowse, modeSelect } from './index'; +import { actionAddEntity } from '../actions/index'; +import { behaviorDraw } from '../behavior/index'; +import { coreNode } from '../core/index'; -export function AddPoint(context) { + +export function modeAddPoint(context) { var mode = { id: 'add-point', button: 'point', @@ -13,7 +14,7 @@ export function AddPoint(context) { key: '1' }; - var behavior = Draw(context) + var behavior = behavriorDraw(context) .tail(t('modes.add_point.tail')) .on('click', add) .on('clickWay', addWay) @@ -21,38 +22,45 @@ export function AddPoint(context) { .on('cancel', cancel) .on('finish', cancel); + function add(loc) { - var node = Node({loc: loc}); + var node = coreNode({ loc: loc }); context.perform( - AddEntity(node), - t('operations.add.annotation.point')); + actionAddEntity(node), + t('operations.add.annotation.point') + ); context.enter( - Select(context, [node.id]) - .suppressMenu(true) - .newFeature(true)); + modeSelect(context, [node.id]).suppressMenu(true).newFeature(true) + ); } + function addWay(loc) { add(loc); } + function addNode(node) { add(node.loc); } + function cancel() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + mode.enter = function() { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + return mode; } diff --git a/modules/modes/browse.js b/modules/modes/browse.js index 195a7bca6..5552d91d6 100644 --- a/modules/modes/browse.js +++ b/modules/modes/browse.js @@ -1,8 +1,16 @@ import { t } from '../util/locale'; -import { Hover, Lasso, Paste, Select } from '../behavior/index'; -import { DragNode } from './index'; -export function Browse(context) { +import { + behaviorHover, + behaviorLasso, + behaviorPaste, + behaviorSelect +} from '../behavior/index'; + +import { modeDragNode } from './index'; + + +export function modeBrowse(context) { var mode = { button: 'browse', id: 'browse', @@ -11,12 +19,13 @@ export function Browse(context) { }, sidebar; var behaviors = [ - Paste(context), - Hover(context) - .on('hover', context.ui().sidebar.hover), - Select(context), - Lasso(context), - DragNode(context).behavior]; + behaviorPaste(context), + behaviorHover(context).on('hover', context.ui().sidebar.hover), + behaviorSelect(context), + behaviorLasso(context), + modeDragNode(context).behavior + ]; + mode.enter = function() { behaviors.forEach(function(behavior) { @@ -35,6 +44,7 @@ export function Browse(context) { } }; + mode.exit = function() { context.ui().sidebar.hover.cancel(); behaviors.forEach(function(behavior) { @@ -46,11 +56,13 @@ export function Browse(context) { } }; + mode.sidebar = function(_) { if (!arguments.length) return sidebar; sidebar = _; return mode; }; + return mode; } diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index 6922e7f7b..d526d32ac 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -1,14 +1,30 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../util/locale'; -import { AddMidpoint, Connect, MoveNode, Noop } from '../actions/index'; -import { Browse, Select } from './index'; -import { Edit, Hover, drag } from '../behavior/index'; -import { Node } from '../core/index'; -import { chooseEdge } from '../geo/index'; -import { entitySelector } from '../util/index'; +import { + actionAddMidpoint, + actionConnect, + actionMoveNode, + actionNoop +} from '../actions/index'; -export function DragNode(context) { +import { + behaviorEdit, + behaviorHover, + behaviorDrag +} from '../behavior/index'; + +import { + modeBrowse, + modeSelect +} from './index'; + +import { coreNode } from '../core/index'; +import { geoChooseEdge } from '../geo/index'; +import { utilEntitySelector } from '../util/index'; + + +export function modeDragNode(context) { var mode = { id: 'drag-node', button: 'browse' @@ -17,12 +33,11 @@ export function DragNode(context) { var nudgeInterval, activeIDs, wasMidpoint, - cancelled, + isCancelled, selectedIDs = [], - hover = Hover(context) - .altDisables(true) - .on('hover', context.ui().sidebar.hover), - edit = Edit(context); + hover = behaviorHover(context).altDisables(true).on('hover', context.ui().sidebar.hover), + edit = behaviorEdit(context); + function edge(point, size) { var pad = [30, 100, 30, 100]; @@ -33,6 +48,7 @@ export function DragNode(context) { return null; } + function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { @@ -40,33 +56,38 @@ export function DragNode(context) { }, 50); } + function stopNudge() { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = null; } + function moveAnnotation(entity) { return t('operations.move.annotation.' + entity.geometry(context.graph())); } + function connectAnnotation(entity) { return t('operations.connect.annotation.' + entity.geometry(context.graph())); } + function origin(entity) { return context.projection(entity.loc); } + function start(entity) { - cancelled = d3.event.sourceEvent.shiftKey || + isCancelled = d3.event.sourceEvent.shiftKey || context.features().hasHiddenConnections(entity, context.graph()); - if (cancelled) return behavior.cancel(); + if (isCancelled) return behavior.cancel(); wasMidpoint = entity.type === 'midpoint'; if (wasMidpoint) { var midpoint = entity; - entity = Node(); + entity = coreNode(); context.perform(AddMidpoint(midpoint, entity)); var vertex = context.surface() @@ -75,15 +96,15 @@ export function DragNode(context) { } else { context.perform( - Noop()); + actionNoop()); } activeIDs = _.map(context.graph().parentWays(entity), 'id'); activeIDs.push(entity.id); - context.enter(mode); } + function datum() { if (d3.event.sourceEvent.altKey) { return {}; @@ -92,6 +113,7 @@ export function DragNode(context) { return d3.event.sourceEvent.target.__data__ || {}; } + // via https://gist.github.com/shawnbot/4166283 function childOf(p, c) { if (p === c) return false; @@ -99,8 +121,9 @@ export function DragNode(context) { return c === p; } + function move(entity) { - if (cancelled) return; + if (isCancelled) return; d3.event.sourceEvent.stopPropagation(); var nudge = childOf(context.container().node(), @@ -116,39 +139,45 @@ export function DragNode(context) { if (d.type === 'node' && d.id !== entity.id) { loc = d.loc; } else if (d.type === 'way' && !d3.select(d3.event.sourceEvent.target).classed('fill')) { - loc = chooseEdge(context.childNodes(d), context.mouse(), context.projection).loc; + loc = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection).loc; } context.replace( - MoveNode(entity.id, loc), - moveAnnotation(entity)); + actionMoveNode(entity.id, loc), + moveAnnotation(entity) + ); } + function end(entity) { - if (cancelled) return; + if (isCancelled) return; var d = datum(); if (d.type === 'way') { - var choice = chooseEdge(context.childNodes(d), context.mouse(), context.projection); + var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection); context.replace( - AddMidpoint({ loc: choice.loc, edge: [d.nodes[choice.index - 1], d.nodes[choice.index]] }, entity), - connectAnnotation(d)); + actionAddMidpoint({ loc: choice.loc, edge: [d.nodes[choice.index - 1], d.nodes[choice.index]] }, entity), + connectAnnotation(d) + ); } else if (d.type === 'node' && d.id !== entity.id) { context.replace( - Connect([d.id, entity.id]), - connectAnnotation(d)); + actionConnect([d.id, entity.id]), + connectAnnotation(d) + ); } else if (wasMidpoint) { context.replace( - Noop(), - t('operations.add.annotation.vertex')); + actionNoop(), + t('operations.add.annotation.vertex') + ); } else { context.replace( - Noop(), - moveAnnotation(entity)); + actionNoop(), + moveAnnotation(entity) + ); } var reselection = selectedIDs.filter(function(id) { @@ -156,25 +185,26 @@ export function DragNode(context) { }); if (reselection.length) { - context.enter( - Select(context, reselection) - .suppressMenu(true)); + context.enter(modeSelect(context, reselection).suppressMenu(true)); } else { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } + function cancel() { behavior.cancel(); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + function setActiveElements() { - context.surface().selectAll(entitySelector(activeIDs)) + context.surface().selectAll(utilEntitySelector(activeIDs)) .classed('active', true); } - var behavior = drag() + + var behavior = behaviorDrag() .delegate('g.node, g.point, g.midpoint') .surface(context.surface().node()) .origin(origin) @@ -182,6 +212,7 @@ export function DragNode(context) { .on('move', move) .on('end', end); + mode.enter = function() { context.install(hover); context.install(edit); @@ -195,6 +226,7 @@ export function DragNode(context) { setActiveElements(); }; + mode.exit = function() { context.ui().sidebar.hover.cancel(); context.uninstall(hover); @@ -213,13 +245,16 @@ export function DragNode(context) { stopNudge(); }; + mode.selectedIDs = function(_) { if (!arguments.length) return selectedIDs; selectedIDs = _; return mode; }; + mode.behavior = behavior; + return mode; } diff --git a/modules/modes/draw_area.js b/modules/modes/draw_area.js index 28ca632df..763a45944 100644 --- a/modules/modes/draw_area.js +++ b/modules/modes/draw_area.js @@ -1,7 +1,7 @@ import { t } from '../util/locale'; -import { DrawWay } from '../behavior/index'; +import { behaviorDrawWay } from '../behavior/index'; -export function DrawArea(context, wayId, baseGraph) { +export function modeDrawArea(context, wayId, baseGraph) { var mode = { button: 'area', id: 'draw-area' @@ -9,12 +9,13 @@ export function DrawArea(context, wayId, baseGraph) { var behavior; + mode.enter = function() { var way = context.entity(wayId), headId = way.nodes[way.nodes.length - 2], tailId = way.first(); - behavior = DrawWay(context, wayId, -1, mode, baseGraph) + behavior = behaviorDrawWay(context, wayId, -1, mode, baseGraph) .tail(t('modes.draw_area.tail')); var addNode = behavior.addNode; @@ -30,13 +31,16 @@ export function DrawArea(context, wayId, baseGraph) { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + mode.selectedIDs = function() { return [wayId]; }; + return mode; } diff --git a/modules/modes/draw_line.js b/modules/modes/draw_line.js index 9c8691123..27058594a 100644 --- a/modules/modes/draw_line.js +++ b/modules/modes/draw_line.js @@ -1,7 +1,7 @@ import { t } from '../util/locale'; -import { DrawWay } from '../behavior/index'; +import { behaviorDrawWay } from '../behavior/index'; -export function DrawLine(context, wayId, baseGraph, affix) { +export function modeDrawLine(context, wayId, baseGraph, affix) { var mode = { button: 'line', id: 'draw-line' @@ -9,12 +9,13 @@ export function DrawLine(context, wayId, baseGraph, affix) { var behavior; + mode.enter = function() { var way = context.entity(wayId), index = (affix === 'prefix') ? 0 : undefined, headId = (affix === 'prefix') ? way.first() : way.last(); - behavior = DrawWay(context, wayId, index, mode, baseGraph) + behavior = behaviorDrawWay(context, wayId, index, mode, baseGraph) .tail(t('modes.draw_line.tail')); var addNode = behavior.addNode; @@ -30,13 +31,16 @@ export function DrawLine(context, wayId, baseGraph, affix) { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + mode.selectedIDs = function() { return [wayId]; }; + return mode; } diff --git a/modules/modes/index.js b/modules/modes/index.js index 046d6bcae..98cefa8c1 100644 --- a/modules/modes/index.js +++ b/modules/modes/index.js @@ -1,11 +1,11 @@ -export { AddArea } from './add_area'; -export { AddLine } from './add_line'; -export { AddPoint } from './add_point'; -export { Browse } from './browse'; -export { DragNode } from './drag_node'; -export { DrawArea } from './draw_area'; -export { DrawLine } from './draw_line'; -export { Move } from './move'; -export { RotateWay } from './rotate_way'; -export { Save } from './save'; -export { Select } from './select'; +export { modeAddArea } from './add_area'; +export { modeAddLine } from './add_line'; +export { modeAddPoint } from './add_point'; +export { modeBrowse } from './browse'; +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 { modeSave } from './save'; +export { modeSelect } from './select'; diff --git a/modules/modes/move.js b/modules/modes/move.js index 9e424038d..6dbff9502 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -1,18 +1,19 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { Browse, Select } from './index'; -import { Move as MoveAction, Noop } from '../actions/index'; -import { Edit } from '../behavior/index'; +import { modeBrowse, modeSelect } from './index'; +import { actionMove, actionNoop } from '../actions/index'; +import { behaviorEdit } from '../behavior/index'; -export function Move(context, entityIDs, baseGraph) { + +export function modeMove(context, entityIDs, baseGraph) { var mode = { id: 'move', button: 'browse' }; var keybinding = d3keybinding('move'), - edit = Edit(context), + edit = behaviorEdit(context), annotation = entityIDs.length === 1 ? t('operations.move.annotation.' + context.geometry(entityIDs[0])) : t('operations.move.annotation.multiple'), @@ -20,8 +21,10 @@ export function Move(context, entityIDs, baseGraph) { origin, nudgeInterval; + 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]; @@ -31,6 +34,7 @@ export function Move(context, entityIDs, baseGraph) { return null; } + function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { @@ -39,23 +43,25 @@ export function Move(context, entityIDs, baseGraph) { var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(vecSub(currMouse, origMouse), nudge), - action = MoveAction(entityIDs, delta, context.projection, cache); + action = actionMove(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); }, 50); } + function stopNudge() { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = null; } + function move() { var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(currMouse, origMouse), - action = MoveAction(entityIDs, delta, context.projection, cache); + action = actionMove(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); @@ -64,27 +70,31 @@ export function Move(context, entityIDs, baseGraph) { else stopNudge(); } + function finish() { d3.event.stopPropagation(); - context.enter(Select(context, entityIDs).suppressMenu(true)); + context.enter(modeSelect(context, entityIDs).suppressMenu(true)); stopNudge(); } + function cancel() { if (baseGraph) { while (context.graph() !== baseGraph) context.pop(); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } else { context.pop(); - context.enter(Select(context, entityIDs).suppressMenu(true)); + context.enter(modeSelect(context, entityIDs).suppressMenu(true)); } stopNudge(); } + function undone() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + mode.enter = function() { origin = context.map().mouseCoordinates(); cache = {}; @@ -92,8 +102,9 @@ export function Move(context, entityIDs, baseGraph) { context.install(edit); context.perform( - Noop(), - annotation); + actionNoop(), + annotation + ); context.surface() .on('mousemove.move', move) @@ -110,6 +121,7 @@ export function Move(context, entityIDs, baseGraph) { .call(keybinding); }; + mode.exit = function() { stopNudge(); @@ -125,5 +137,6 @@ export function Move(context, entityIDs, baseGraph) { keybinding.off(); }; + return mode; } diff --git a/modules/modes/rotate_way.js b/modules/modes/rotate_way.js index 43cbe1578..80cf0147c 100644 --- a/modules/modes/rotate_way.js +++ b/modules/modes/rotate_way.js @@ -2,18 +2,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { Browse, Select } from './index'; -import { Noop, RotateWay as RotateWayAction } from '../actions/index'; -import { Edit } from '../behavior/index'; +import { modeBrowse, modeSelect } from './index'; +import { actionNoop, actionRotateWay } from '../actions/index'; +import { behaviorEdit } from '../behavior/index'; -export function RotateWay(context, wayId) { + +export function modeRotateWay(context, wayId) { var mode = { id: 'rotate-way', button: 'browse' }; var keybinding = d3keybinding('rotate-way'), - edit = Edit(context); + edit = behaviorEdit(context); + mode.enter = function() { context.install(edit); @@ -26,38 +28,9 @@ export function RotateWay(context, wayId) { angle; context.perform( - Noop(), - annotation); - - 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( - RotateWayAction(wayId, pivot, newAngle - angle, context.projection), - annotation); - - angle = newAngle; - } - - function finish() { - d3.event.stopPropagation(); - context.enter(Select(context, [wayId]) - .suppressMenu(true)); - } - - function cancel() { - context.pop(); - context.enter(Select(context, [wayId]) - .suppressMenu(true)); - } - - function undone() { - context.enter(Browse(context)); - } + actionNoop(), + annotation + ); context.surface() .on('mousemove.rotate-way', rotate) @@ -72,8 +45,41 @@ export function RotateWay(context, wayId) { 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); @@ -87,5 +93,6 @@ export function RotateWay(context, wayId) { keybinding.off(); }; + return mode; } diff --git a/modules/modes/save.js b/modules/modes/save.js index 77d459def..410efcb20 100644 --- a/modules/modes/save.js +++ b/modules/modes/save.js @@ -1,22 +1,48 @@ import * as d3 from 'd3'; import _ from 'lodash'; + import { t } from '../util/locale'; -import { Conflicts, uiconfirm, Commit, Loading, Success } from '../ui/index'; -import { DiscardTags, MergeRemoteChanges, Noop, Revert } from '../actions/index'; -import { displayName, displayType } from '../util/index'; -import { Browse } from './index'; -import { Graph } from '../core/index'; + +import { + uiConflicts, + uiConfirm, + uiCommit, + uiLoading, + uiSuccess +} from '../ui/index'; + +import { + actionDiscardTags, + actionMergeRemoteChanges, + actionNoop, + actionRevert +} from '../actions/index'; + +import { + utilDisplayName, + utilDisplayType +} from '../util/index'; + +import { modeBrowse } from './index'; +import { coreGraph } from '../core/index'; import { JXON } from '../util/jxon'; -export function Save(context) { - var ui = Commit(context) + +export function modeSave(context) { + var mode = { + id: 'save' + }; + + var ui = uiCommit(context) .on('cancel', cancel) .on('save', save); + function cancel() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + function save(e, tryAgain) { function withChildNodes(ids, graph) { return _.uniq(_.reduce(ids, function(result, id) { @@ -35,18 +61,18 @@ export function Save(context) { }, _.clone(ids))); } - var loading = Loading(context).message(t('save.uploading')).blocking(true), + var loading = uiLoading(context).message(t('save.uploading')).blocking(true), history = context.history(), - origChanges = history.changes(DiscardTags(history.difference())), + origChanges = history.changes(actionDiscardTags(history.difference())), localGraph = context.graph(), - remoteGraph = Graph(history.base(), true), + remoteGraph = coreGraph(history.base(), true), modified = _.filter(history.difference().summary(), {changeType: 'modified'}), toCheck = _.map(_.map(modified, 'entity'), 'id'), toLoad = withChildNodes(toCheck, localGraph), conflicts = [], errors = []; - if (!tryAgain) history.perform(Noop()); // checkpoint + if (!tryAgain) history.perform(actionNoop()); // checkpoint context.container().call(loading); if (toCheck.length) { @@ -105,7 +131,7 @@ export function Save(context) { return '' + d + ''; } function entityName(entity) { - return displayName(entity) || (displayType(entity.id) + ' ' + entity.id); + return utilDisplayName(entity) || (utilDisplayType(entity.id) + ' ' + entity.id); } function compareVersions(local, remote) { @@ -131,7 +157,7 @@ export function Save(context) { if (compareVersions(local, remote)) return; - var action = MergeRemoteChanges, + var action = actionMergeRemoteChanges, merge = action(id, localGraph, remoteGraph, formatUser); history.replace(merge); @@ -167,7 +193,7 @@ export function Save(context) { } else if (errors.length) { showErrors(); } else { - var changes = history.changes(DiscardTags(history.difference())); + var changes = history.changes(actionDiscardTags(history.difference())); if (changes.modified.length || changes.created.length || changes.deleted.length) { context.connection().putChangeset( changes, @@ -208,7 +234,7 @@ export function Save(context) { loading.close(); - selection.call(Conflicts(context) + selection.call(uiConflicts(context) .list(conflicts) .on('download', function() { var data = JXON.stringify(context.connection().osmChangeJXON('CHANGEME', origChanges)), @@ -226,10 +252,10 @@ export function Save(context) { if (entity && entity.type === 'way') { var children = _.uniq(entity.nodes); for (var j = 0; j < children.length; j++) { - history.replace(Revert(children[j])); + history.replace(actionRevert(children[j])); } } - history.replace(Revert(conflicts[i].id)); + history.replace(actionRevert(conflicts[i].id)); } } @@ -241,7 +267,7 @@ export function Save(context) { function showErrors() { - var selection = uiconfirm(context.container()); + var selection = uiConfirm(context.container()); history.pop(); loading.close(); @@ -308,20 +334,19 @@ export function Save(context) { function success(e, changeset_id) { - context.enter(Browse(context) - .sidebar(Success(context) + context.enter(modeBrowse(context) + .sidebar(uiSuccess(context) .changeset({ id: changeset_id, comment: e.comment }) .on('cancel', function() { context.ui().sidebar.hide(); - }))); + }) + ) + ); } - var mode = { - id: 'save' - }; mode.enter = function() { context.connection().authenticate(function(err) { @@ -333,6 +358,7 @@ export function Save(context) { }); }; + mode.exit = function() { context.ui().sidebar.hide(); }; diff --git a/modules/modes/select.js b/modules/modes/select.js index e0b6186da..0a74fba82 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -2,17 +2,37 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import * as Operations from '../operations/index'; -import { Breathe, Copy, Hover, Lasso, Paste, Select as SelectBehavior } from '../behavior/index'; -import { Extent, chooseEdge, pointInPolygon } from '../geo/index'; -import { Node, Way } from '../core/index'; -import { RadialMenu, SelectionList } from '../ui/index'; -import { AddMidpoint } from '../actions/index'; -import { Browse } from './browse'; -import { DragNode } from './drag_node'; -import { entityOrMemberSelector } from '../util/index'; -export function Select(context, selectedIDs) { +import { actionAddMidpoint } from '../actions/index'; + +import { + behaviorBreathe, + behaviorCopy, + behaviorHover, + behaviorLasso, + behaviorPaste, + behaviorSelect +} from '../behavior/index'; + +import { + geoExtent, + geoChooseEdge, + geoPointInPolygon +} from '../geo/index'; + +import { + coreNode, + coreWay +} from '../core/index'; + +import { modeBrowse } from './browse'; +import { modeDragNode } from './drag_node'; +import * as Operations from '../operations/index'; +import { uiRadialMenu, uiSelectionList } from '../ui/index'; +import { utilEntityOrMemberSelector } from '../util/index'; + + +export function modeSelect(context, selectedIDs) { var mode = { id: 'select', button: 'browse' @@ -21,15 +41,14 @@ export function Select(context, selectedIDs) { var keybinding = d3keybinding('select'), timeout = null, behaviors = [ - Copy(context), - Paste(context), - Breathe(context), - Hover(context), - SelectBehavior(context), - Lasso(context), - DragNode(context) - .selectedIDs(selectedIDs) - .behavior], + behaviorCopy(context), + behaviorPaste(context), + behaviorBreathe(context), + behaviorHover(context), + behaviorSelect(context), + behaviorLasso(context), + modeDragNode(context).selectedIDs(selectedIDs).behavior + ], inspector, radialMenu, newFeature = false, @@ -45,12 +64,14 @@ export function Select(context, selectedIDs) { } } + function closeMenu() { if (radialMenu) { context.surface().call(radialMenu.close); } } + function positionMenu() { if (suppressMenu || !radialMenu) { return; } @@ -61,8 +82,8 @@ export function Select(context, selectedIDs) { radialMenu.center(context.projection(entity.loc)); } else { var point = context.mouse(), - viewport = Extent(context.projection.clipExtent()).polygon(); - if (pointInPolygon(point, viewport)) { + viewport = geoExtent(context.projection.clipExtent()).polygon(); + if (geoPointInPolygon(point, viewport)) { radialMenu.center(point); } else { suppressMenu = true; @@ -70,6 +91,7 @@ export function Select(context, selectedIDs) { } } + function showMenu() { closeMenu(); if (!suppressMenu && radialMenu) { @@ -77,6 +99,7 @@ export function Select(context, selectedIDs) { } } + function toggleMenu() { if (d3.select('.radial-menu').empty()) { showMenu(); @@ -85,13 +108,15 @@ export function Select(context, selectedIDs) { } } + mode.selectedIDs = function() { return selectedIDs; }; + mode.reselect = function() { var surfaceNode = context.surface().node(); - if (surfaceNode.focus) { // FF doesn't support it + if (surfaceNode.focus) { // FF doesn't support it surfaceNode.focus(); } @@ -99,47 +124,54 @@ export function Select(context, selectedIDs) { showMenu(); }; + mode.newFeature = function(_) { if (!arguments.length) return newFeature; newFeature = _; return mode; }; + mode.suppressMenu = function(_) { if (!arguments.length) return suppressMenu; suppressMenu = _; return mode; }; + mode.enter = function() { + function update() { closeMenu(); if (_.some(selectedIDs, function(id) { return !context.hasEntity(id); })) { // Exit mode if selected entity gets undone - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } + function dblclick() { var target = d3.select(d3.event.target), datum = target.datum(); - if (datum instanceof Way && !target.classed('fill')) { - var choice = chooseEdge(context.childNodes(datum), context.mouse(), context.projection), - node = Node(); + if (datum instanceof coreWay && !target.classed('fill')) { + var choice = geoChooseEdge(context.childNodes(datum), context.mouse(), context.projection), + node = coreNode(); var prev = datum.nodes[choice.index - 1], next = datum.nodes[choice.index]; context.perform( - AddMidpoint({loc: choice.loc, edge: [prev, next]}, node), - t('operations.add.annotation.vertex')); + actionAddMidpoint({loc: choice.loc, edge: [prev, next]}, node), + t('operations.add.annotation.vertex') + ); d3.event.preventDefault(); d3.event.stopPropagation(); } } + function selectElements(drawn) { var entity = singular(); if (entity && context.geometry(entity.id) === 'relation') { @@ -148,11 +180,11 @@ export function Select(context, selectedIDs) { } var selection = context.surface() - .selectAll(entityOrMemberSelector(selectedIDs, context.graph())); + .selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph())); if (selection.empty()) { if (drawn) { // Exit mode if selected DOM elements have disappeared.. - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } else { selection @@ -160,9 +192,10 @@ export function Select(context, selectedIDs) { } } + function esc() { if (!context.inIntro()) { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } @@ -171,11 +204,11 @@ export function Select(context, selectedIDs) { context.install(behavior); }); - var operations = _.without(d3.values(Operations), Operations.Delete) + var operations = _.without(d3.values(Operations), Operations.operationDelete) .map(function(o) { return o(selectedIDs, context); }) .filter(function(o) { return o.available(); }); - operations.unshift(Operations.Delete(selectedIDs, context)); + operations.unshift(Operations.operationDelete(selectedIDs, context)); keybinding .on('⎋', esc, true) @@ -194,7 +227,7 @@ export function Select(context, selectedIDs) { d3.select(document) .call(keybinding); - radialMenu = RadialMenu(context, operations); + radialMenu = uiRadialMenu(context, operations); context.ui().sidebar .select(singular() ? singular().id : null, newFeature); @@ -225,11 +258,12 @@ export function Select(context, selectedIDs) { }, 200); if (selectedIDs.length > 1) { - var entities = SelectionList(context, selectedIDs); + var entities = uiSelectionList(context, selectedIDs); context.ui().sidebar.show(entities); } }; + mode.exit = function() { if (timeout) window.clearTimeout(timeout); @@ -256,5 +290,6 @@ export function Select(context, selectedIDs) { context.ui().sidebar.hide(); }; + return mode; } diff --git a/modules/operations/circularize.js b/modules/operations/circularize.js index 420d7605a..475523ecb 100644 --- a/modules/operations/circularize.js +++ b/modules/operations/circularize.js @@ -1,25 +1,28 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Circularize as CircularizeAction } from '../actions/index'; +import { actionCircularize } from '../actions/index'; -export function Circularize(selectedIDs, context) { + +export function operationCircularize(selectedIDs, context) { var entityId = selectedIDs[0], entity = context.entity(entityId), extent = entity.extent(context.graph()), geometry = context.geometry(entityId), - action = CircularizeAction(entityId, context.projection); + action = actionCircularize(entityId, context.projection); var operation = function() { var annotation = t('operations.circularize.annotation.' + geometry); context.perform(action, annotation); }; + operation.available = function() { return selectedIDs.length === 1 && entity.type === 'way' && _.uniq(entity.nodes).length > 1; }; + operation.disabled = function() { var reason; if (extent.percentContainedIn(context.extent()) < 0.8) { @@ -30,6 +33,7 @@ export function Circularize(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -37,9 +41,11 @@ export function Circularize(selectedIDs, context) { t('operations.circularize.description.' + geometry); }; + operation.id = 'circularize'; operation.keys = [t('operations.circularize.key')]; operation.title = t('operations.circularize.title'); + return operation; } diff --git a/modules/operations/continue.js b/modules/operations/continue.js index 13758ade0..e0b286d83 100644 --- a/modules/operations/continue.js +++ b/modules/operations/continue.js @@ -1,14 +1,16 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { DrawLine } from '../modes/index'; +import { modeDrawLine } from '../modes/index'; -export function Continue(selectedIDs, context) { + +export function operationContinue(selectedIDs, context) { var graph = context.graph(), entities = selectedIDs.map(function(id) { return graph.entity(id); }), - geometries = _.extend({line: [], vertex: []}, + geometries = _.extend({ line: [], vertex: [] }, _.groupBy(entities, function(entity) { return entity.geometry(graph); })), vertex = geometries.vertex[0]; + function candidateWays() { return graph.parentWays(vertex).filter(function(parent) { return parent.geometry(graph) === 'line' && @@ -17,20 +19,21 @@ export function Continue(selectedIDs, context) { }); } + var operation = function() { var candidate = candidateWays()[0]; - context.enter(DrawLine( - context, - candidate.id, - context.graph(), - candidate.affix(vertex.id))); + context.enter( + modeDrawLine(context, candidate.id, context.graph(), candidate.affix(vertex.id)) + ); }; + operation.available = function() { return geometries.vertex.length === 1 && geometries.line.length <= 1 && !context.features().hasHiddenConnections(vertex, context.graph()); }; + operation.disabled = function() { var candidates = candidateWays(); if (candidates.length === 0) @@ -39,6 +42,7 @@ export function Continue(selectedIDs, context) { return 'multiple'; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -46,9 +50,11 @@ export function Continue(selectedIDs, context) { t('operations.continue.description'); }; + operation.id = 'continue'; operation.keys = [t('operations.continue.key')]; operation.title = t('operations.continue.title'); + return operation; } diff --git a/modules/operations/delete.js b/modules/operations/delete.js index 21644de2d..6c751978c 100644 --- a/modules/operations/delete.js +++ b/modules/operations/delete.js @@ -1,19 +1,20 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Browse, Select } from '../modes/index'; -import { DeleteMultiple } from '../actions/index'; -import { cmd } from '../ui/index'; -import { sphericalDistance } from '../geo/index'; +import { modeBrowse, modeSelect } from '../modes/index'; +import { actionDeleteMultiple } from '../actions/index'; +import { uiCmd } from '../ui/index'; +import { geoSphericalDistance } from '../geo/index'; -export function Delete(selectedIDs, context) { - var action = DeleteMultiple(selectedIDs); + +export function operationDelete(selectedIDs, context) { + var action = actionDeleteMultiple(selectedIDs); var operation = function() { var annotation, nextSelectedID; if (selectedIDs.length > 1) { - annotation = t('operations.delete.annotation.multiple', {n: selectedIDs.length}); + annotation = t('operations.delete.annotation.multiple', { n: selectedIDs.length }); } else { var id = selectedIDs[0], @@ -34,8 +35,8 @@ export function Delete(selectedIDs, context) { } else if (i === nodes.length - 1) { i--; } else { - var a = sphericalDistance(entity.loc, context.entity(nodes[i - 1]).loc), - b = sphericalDistance(entity.loc, context.entity(nodes[i + 1]).loc); + var a = geoSphericalDistance(entity.loc, context.entity(nodes[i - 1]).loc), + b = geoSphericalDistance(entity.loc, context.entity(nodes[i + 1]).loc); i = a < b ? i - 1 : i + 1; } @@ -44,20 +45,20 @@ export function Delete(selectedIDs, context) { } if (nextSelectedID && context.hasEntity(nextSelectedID)) { - context.enter(Select(context, [nextSelectedID])); + context.enter(modeSelect(context, [nextSelectedID])); } else { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } - context.perform( - action, - annotation); + context.perform(action, annotation); }; + operation.available = function() { return true; }; + operation.disabled = function() { var reason; if (_.some(selectedIDs, context.hasHiddenConnections)) { @@ -66,6 +67,7 @@ export function Delete(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -73,9 +75,11 @@ export function Delete(selectedIDs, context) { t('operations.delete.description'); }; + operation.id = 'delete'; - operation.keys = [cmd('⌘⌫'), cmd('⌘⌦')]; + operation.keys = [uiCmd('⌘⌫'), uiCmd('⌘⌦')]; operation.title = t('operations.delete.title'); + return operation; } diff --git a/modules/operations/disconnect.js b/modules/operations/disconnect.js index 9adcf74a4..842aa4afa 100644 --- a/modules/operations/disconnect.js +++ b/modules/operations/disconnect.js @@ -1,14 +1,15 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Disconnect as DisconnectAction } from '../actions/index'; +import { actionDisconnect } from '../actions/index'; -export function Disconnect(selectedIDs, context) { - var vertices = _.filter(selectedIDs, function vertex(entityId) { + +export function operationDisconnect(selectedIDs, context) { + var vertices = _.filter(selectedIDs, function(entityId) { return context.geometry(entityId) === 'vertex'; }); var entityId = vertices[0], - action = DisconnectAction(entityId); + action = actionDisconnect(entityId); if (selectedIDs.length > 1) { action.limitWays(_.without(selectedIDs, entityId)); @@ -18,10 +19,12 @@ export function Disconnect(selectedIDs, context) { context.perform(action, t('operations.disconnect.annotation')); }; + operation.available = function() { return vertices.length === 1; }; + operation.disabled = function() { var reason; if (_.some(selectedIDs, context.hasHiddenConnections)) { @@ -30,6 +33,7 @@ export function Disconnect(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -37,9 +41,11 @@ export function Disconnect(selectedIDs, context) { t('operations.disconnect.description'); }; + operation.id = 'disconnect'; operation.keys = [t('operations.disconnect.key')]; operation.title = t('operations.disconnect.title'); + return operation; } diff --git a/modules/operations/index.js b/modules/operations/index.js index 58190dbea..bd4058f5d 100644 --- a/modules/operations/index.js +++ b/modules/operations/index.js @@ -1,11 +1,11 @@ -export { Circularize } from './circularize'; -export { Continue } from './continue'; -export { Delete } from './delete'; -export { Disconnect } from './disconnect'; -export { Merge } from './merge'; -export { Move } from './move'; -export { Orthogonalize } from './orthogonalize'; -export { Reverse } from './reverse'; -export { Rotate } from './rotate'; -export { Split } from './split'; -export { Straighten } from './straighten'; +export { operationCircularize } from './circularize'; +export { operationContinue } from './continue'; +export { operationDelete } from './delete'; +export { operationDisconnect } from './disconnect'; +export { operationMerge } from './merge'; +export { operationMove } from './move'; +export { operationOrthogonalize } from './orthogonalize'; +export { operationReverse } from './reverse'; +export { operationRotate } from './rotate'; +export { operationSplit } from './split'; +export { operationStraighten } from './straighten'; diff --git a/modules/operations/merge.js b/modules/operations/merge.js index de23c445a..345440cce 100644 --- a/modules/operations/merge.js +++ b/modules/operations/merge.js @@ -1,11 +1,17 @@ import { t } from '../util/locale'; -import { Join, Merge as MergeAction, MergePolygon } from '../actions/index'; -import { Select } from '../modes/index'; +import { + actionJoin, + actionMerge, + actionMergePolygon +} from '../actions/index'; -export function Merge(selectedIDs, context) { - var join = Join(selectedIDs), - merge = MergeAction(selectedIDs), - mergePolygon = MergePolygon(selectedIDs); +import { modeSelect } from '../modes/index'; + + +export function operationMerge(selectedIDs, context) { + var join = actionJoin(selectedIDs), + merge = actionMerge(selectedIDs), + mergePolygon = actionMergePolygon(selectedIDs); var operation = function() { var annotation = t('operations.merge.annotation', {n: selectedIDs.length}), @@ -20,40 +26,49 @@ export function Merge(selectedIDs, context) { } context.perform(action, annotation); - context.enter(Select(context, selectedIDs.filter(function(id) { return context.hasEntity(id); })) - .suppressMenu(true)); + var ids = selectedIDs.filter(function(id) { return context.hasEntity(id); }); + context.enter(modeSelect(context, ids).suppressMenu(true)); }; + operation.available = function() { return selectedIDs.length >= 2; }; + operation.disabled = function() { return join.disabled(context.graph()) && merge.disabled(context.graph()) && mergePolygon.disabled(context.graph()); }; + operation.tooltip = function() { var j = join.disabled(context.graph()), m = merge.disabled(context.graph()), p = mergePolygon.disabled(context.graph()); - if (j === 'restriction' && m && p) - return t('operations.merge.restriction', {relation: context.presets().item('type/restriction').name()}); + if (j === 'restriction' && m && p) { + return t('operations.merge.restriction', + { relation: context.presets().item('type/restriction').name() }); + } - if (p === 'incomplete_relation' && j && m) + if (p === 'incomplete_relation' && j && m) { return t('operations.merge.incomplete_relation'); + } - if (j && m && p) + if (j && m && p) { return t('operations.merge.' + j); + } return t('operations.merge.description'); }; + operation.id = 'merge'; operation.keys = [t('operations.merge.key')]; operation.title = t('operations.merge.title'); + return operation; } diff --git a/modules/operations/move.js b/modules/operations/move.js index 9ef9b5e99..69a2f2a4b 100644 --- a/modules/operations/move.js +++ b/modules/operations/move.js @@ -1,23 +1,26 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Extent } from '../geo/index'; -import { Move as MoveAction } from '../actions/index'; -import { Move as MoveMode } from '../modes/index'; +import { geoExtent } from '../geo/index'; +import { actionMove } from '../actions/index'; +import { modeMove } from '../modes/index'; -export function Move(selectedIDs, context) { + +export function operationMove(selectedIDs, context) { var extent = selectedIDs.reduce(function(extent, id) { return extent.extend(context.entity(id).extent(context.graph())); - }, Extent()); + }, geoExtent()); var operation = function() { - context.enter(MoveMode(context, selectedIDs)); + context.enter(modeMove(context, selectedIDs)); }; + operation.available = function() { return selectedIDs.length > 1 || context.entity(selectedIDs[0]).type !== 'node'; }; + operation.disabled = function() { var reason; if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) { @@ -25,9 +28,11 @@ export function Move(selectedIDs, context) { } else if (_.some(selectedIDs, context.hasHiddenConnections)) { reason = 'connected_to_hidden'; } - return MoveAction(selectedIDs).disabled(context.graph()) || reason; + + return actionMove(selectedIDs).disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -35,9 +40,11 @@ export function Move(selectedIDs, context) { t('operations.move.description'); }; + operation.id = 'move'; operation.keys = [t('operations.move.key')]; operation.title = t('operations.move.title'); + return operation; } diff --git a/modules/operations/orthogonalize.js b/modules/operations/orthogonalize.js index 31bd58bd4..b8143edce 100644 --- a/modules/operations/orthogonalize.js +++ b/modules/operations/orthogonalize.js @@ -1,19 +1,21 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Orthogonalize as OrthogonalizeAction } from '../actions/index'; +import { actionOrthogonalize } from '../actions/index'; -export function Orthogonalize(selectedIDs, context) { + +export function operationOrthogonalize(selectedIDs, context) { var entityId = selectedIDs[0], entity = context.entity(entityId), extent = entity.extent(context.graph()), geometry = context.geometry(entityId), - action = OrthogonalizeAction(entityId, context.projection); + action = actionOrthogonalize(entityId, context.projection); var operation = function() { var annotation = t('operations.orthogonalize.annotation.' + geometry); context.perform(action, annotation); }; + operation.available = function() { return selectedIDs.length === 1 && entity.type === 'way' && @@ -21,6 +23,7 @@ export function Orthogonalize(selectedIDs, context) { _.uniq(entity.nodes).length > 2; }; + operation.disabled = function() { var reason; if (extent.percentContainedIn(context.extent()) < 0.8) { @@ -31,6 +34,7 @@ export function Orthogonalize(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -38,9 +42,11 @@ export function Orthogonalize(selectedIDs, context) { t('operations.orthogonalize.description.' + geometry); }; + operation.id = 'orthogonalize'; operation.keys = [t('operations.orthogonalize.key')]; operation.title = t('operations.orthogonalize.title'); + return operation; } diff --git a/modules/operations/reverse.js b/modules/operations/reverse.js index 65ee1c2ec..6c75df732 100644 --- a/modules/operations/reverse.js +++ b/modules/operations/reverse.js @@ -1,31 +1,38 @@ import { t } from '../util/locale'; -import { Reverse as ReverseAction } from '../actions/index'; +import { actionReverse } from '../actions/index'; -export function Reverse(selectedIDs, context) { + +export function operationReverse(selectedIDs, context) { var entityId = selectedIDs[0]; var operation = function() { context.perform( - ReverseAction(entityId), - t('operations.reverse.annotation')); + actionReverse(entityId), + t('operations.reverse.annotation') + ); }; + operation.available = function() { return selectedIDs.length === 1 && context.geometry(entityId) === 'line'; }; + operation.disabled = function() { return false; }; + operation.tooltip = function() { return t('operations.reverse.description'); }; + operation.id = 'reverse'; operation.keys = [t('operations.reverse.key')]; operation.title = t('operations.reverse.title'); + return operation; } diff --git a/modules/operations/rotate.js b/modules/operations/rotate.js index 9c4e1988a..42cadfd1c 100644 --- a/modules/operations/rotate.js +++ b/modules/operations/rotate.js @@ -1,16 +1,18 @@ import { t } from '../util/locale'; -import { RotateWay } from '../modes/index'; +import { modeRotateWay } from '../modes/index'; -export function Rotate(selectedIDs, context) { + +export function operationRotate(selectedIDs, context) { var entityId = selectedIDs[0], entity = context.entity(entityId), extent = entity.extent(context.graph()), geometry = context.geometry(entityId); var operation = function() { - context.enter(RotateWay(context, entityId)); + context.enter(modeRotateWay(context, entityId)); }; + operation.available = function() { if (selectedIDs.length !== 1 || entity.type !== 'way') return false; @@ -22,6 +24,7 @@ export function Rotate(selectedIDs, context) { return false; }; + operation.disabled = function() { if (extent.percentContainedIn(context.extent()) < 0.8) { return 'too_large'; @@ -32,6 +35,7 @@ export function Rotate(selectedIDs, context) { } }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -39,9 +43,11 @@ export function Rotate(selectedIDs, context) { t('operations.rotate.description'); }; + operation.id = 'rotate'; operation.keys = [t('operations.rotate.key')]; operation.title = t('operations.rotate.title'); + return operation; } diff --git a/modules/operations/split.js b/modules/operations/split.js index 72b11a73e..0f6aba352 100644 --- a/modules/operations/split.js +++ b/modules/operations/split.js @@ -1,20 +1,22 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Select } from '../modes/index'; -import { Split as SplitAction } from '../actions/index'; +import { modeSelect } from '../modes/index'; +import { actionSplit } from '../actions/index'; -export function Split(selectedIDs, context) { - var vertices = _.filter(selectedIDs, function vertex(entityId) { + +export function operationSplit(selectedIDs, context) { + var vertices = _.filter(selectedIDs, function(entityId) { return context.geometry(entityId) === 'vertex'; }); var entityId = vertices[0], - action = SplitAction(entityId); + action = actionSplit(entityId); if (selectedIDs.length > 1) { action.limitWays(_.without(selectedIDs, entityId)); } + var operation = function() { var annotation; @@ -26,13 +28,15 @@ export function Split(selectedIDs, context) { } var difference = context.perform(action, annotation); - context.enter(Select(context, difference.extantIDs())); + context.enter(modeSelect(context, difference.extantIDs())); }; + operation.available = function() { return vertices.length === 1; }; + operation.disabled = function() { var reason; if (_.some(selectedIDs, context.hasHiddenConnections)) { @@ -41,6 +45,7 @@ export function Split(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); if (disable) { @@ -55,9 +60,11 @@ export function Split(selectedIDs, context) { } }; + operation.id = 'split'; operation.keys = [t('operations.split.key')]; operation.title = t('operations.split.title'); + return operation; } diff --git a/modules/operations/straighten.js b/modules/operations/straighten.js index fd79b8cc6..e0d42cb8d 100644 --- a/modules/operations/straighten.js +++ b/modules/operations/straighten.js @@ -1,16 +1,19 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Straighten as StraightenAction } from '../actions/index'; +import { actionStraighten } from '../actions/index'; -export function Straighten(selectedIDs, context) { + +export function operationStraighten(selectedIDs, context) { var entityId = selectedIDs[0], - action = StraightenAction(entityId, context.projection); + action = actionStraighten(entityId, context.projection); + function operation() { var annotation = t('operations.straighten.annotation'); context.perform(action, annotation); } + operation.available = function() { var entity = context.entity(entityId); return selectedIDs.length === 1 && @@ -19,6 +22,7 @@ export function Straighten(selectedIDs, context) { _.uniq(entity.nodes).length > 2; }; + operation.disabled = function() { var reason; if (context.hasHiddenConnections(entityId)) { @@ -27,6 +31,7 @@ export function Straighten(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -34,9 +39,11 @@ export function Straighten(selectedIDs, context) { t('operations.straighten.description'); }; + operation.id = 'straighten'; operation.keys = [t('operations.straighten.key')]; operation.title = t('operations.straighten.title'); + return operation; } diff --git a/modules/presets/category.js b/modules/presets/category.js index 905938c8f..846e2486a 100644 --- a/modules/presets/category.js +++ b/modules/presets/category.js @@ -1,29 +1,38 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Collection } from './collection'; +import { presetCollection } from './collection'; -export function Category(id, category, all) { + +export function presetCategory(id, category, all) { category = _.clone(category); category.id = id; - category.members = Collection(category.members.map(function(id) { + + category.members = presetCollection(category.members.map(function(id) { return all.item(id); })); + category.matchGeometry = function(geometry) { return category.geometry.indexOf(geometry) >= 0; }; - category.matchScore = function() { return -1; }; + + category.matchScore = function() { + return -1; + }; + category.name = function() { return t('presets.categories.' + id + '.name', {'default': id}); }; + category.terms = function() { return []; }; + return category; } diff --git a/modules/presets/collection.js b/modules/presets/collection.js index 012872368..6fa93848d 100644 --- a/modules/presets/collection.js +++ b/modules/presets/collection.js @@ -1,7 +1,8 @@ import _ from 'lodash'; -import { editDistance } from '../util/index'; +import { utilEditDistance } from '../util/index'; -export function Collection(collection) { + +export function presetCollection(collection) { var maxSearchResults = 50, maxSuggestionResults = 10; @@ -9,18 +10,21 @@ export function Collection(collection) { collection: collection, + item: function(id) { return _.find(collection, function(d) { return d.id === id; }); }, + matchGeometry: function(geometry) { - return Collection(collection.filter(function(d) { + return presetCollection(collection.filter(function(d) { return d.matchGeometry(geometry); })); }, + search: function(value, geometry) { if (!value) return this; @@ -72,7 +76,7 @@ export function Collection(collection) { var similar_name = searchable.map(function(a) { return { preset: a, - dist: editDistance(value, a.name()) + dist: utilEditDistance(value, a.name()) }; }).filter(function(a) { return a.dist + Math.min(value.length - a.preset.name().length, 0) < 3; @@ -85,7 +89,7 @@ export function Collection(collection) { // finds close matches to value in preset.terms var similar_terms = _.filter(searchable, function(a) { return _.some(a.terms() || [], function(b) { - return editDistance(value, b) + Math.min(value.length - b.length, 0) < 3; + return utilEditDistance(value, b) + Math.min(value.length - b.length, 0) < 3; }); }); @@ -102,7 +106,7 @@ export function Collection(collection) { var similar_suggestions = suggestions.map(function(a) { return { preset: a, - dist: editDistance(value, suggestionName(a.name())) + dist: utilEditDistance(value, suggestionName(a.name())) }; }).filter(function(a) { return a.dist + Math.min(value.length - suggestionName(a.preset.name()).length, 0) < 1; @@ -123,11 +127,10 @@ export function Collection(collection) { similar_suggestions.slice(0, maxSuggestionResults) ).slice(0, maxSearchResults - 1); - return Collection(_.uniq( - results.concat(other) - )); + return presetCollection(_.uniq(results.concat(other))); } }; + return presets; } diff --git a/modules/presets/field.js b/modules/presets/field.js index f9d7e58d7..3a37e4cd6 100644 --- a/modules/presets/field.js +++ b/modules/presets/field.js @@ -1,27 +1,33 @@ import _ from 'lodash'; import { t } from '../util/locale'; -export function Field(id, field) { + +export function presetField(id, field) { field = _.clone(field); field.id = id; + field.matchGeometry = function(geometry) { return !field.geometry || field.geometry === geometry; }; + field.t = function(scope, options) { return t('presets.fields.' + id + '.' + scope, options); }; + field.label = function() { return field.t('label', {'default': id}); }; + var placeholder = field.placeholder; field.placeholder = function() { return field.t('placeholder', {'default': placeholder}); }; + return field; } diff --git a/modules/presets/index.js b/modules/presets/index.js index 9462df5f9..ba2c815f9 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -1,5 +1,5 @@ -export { Category } from './category.js'; -export { Collection } from './collection.js'; -export { Field } from './field.js'; -export { Preset } from './preset.js'; -export { presets } from './presets.js'; +export { presetCategory } from './category.js'; +export { presetCollection } from './collection.js'; +export { presetField } from './field.js'; +export { presetInit } from './init.js'; +export { presetPreset } from './preset.js'; diff --git a/modules/presets/presets.js b/modules/presets/init.js similarity index 80% rename from modules/presets/presets.js rename to modules/presets/init.js index ead783699..1a244c2db 100644 --- a/modules/presets/presets.js +++ b/modules/presets/init.js @@ -1,18 +1,19 @@ import _ from 'lodash'; -import { Category } from './category'; -import { Collection } from './collection'; -import { Field } from './field'; -import { Preset } from './preset'; +import { presetCategory } from './category'; +import { presetCollection } from './collection'; +import { presetField } from './field'; +import { presetPreset } from './preset'; -export function presets() { - // an iD.presets.Collection with methods for + +export function presetInit() { + // a presetCollection with methods for // loading new data and returning defaults - var all = Collection([]), + var all = presetCollection([]), defaults = { area: all, line: all, point: all, vertex: all, relation: all }, fields = {}, universal = [], - recent = Collection([]); + recent = presetCollection([]); // Index of presets by (geometry, tag key). var index = { @@ -99,31 +100,31 @@ export function presets() { if (d.fields) { _.forEach(d.fields, function(d, id) { - fields[id] = Field(id, d); + fields[id] = presetField(id, d); if (d.universal) universal.push(fields[id]); }); } if (d.presets) { _.forEach(d.presets, function(d, id) { - all.collection.push(Preset(id, d, fields)); + all.collection.push(presetPreset(id, d, fields)); }); } if (d.categories) { _.forEach(d.categories, function(d, id) { - all.collection.push(Category(id, d, all)); + all.collection.push(presetCategory(id, d, all)); }); } if (d.defaults) { var getItem = _.bind(all.item, all); defaults = { - area: Collection(d.defaults.area.map(getItem)), - line: Collection(d.defaults.line.map(getItem)), - point: Collection(d.defaults.point.map(getItem)), - vertex: Collection(d.defaults.vertex.map(getItem)), - relation: Collection(d.defaults.relation.map(getItem)) + area: presetCollection(d.defaults.area.map(getItem)), + line: presetCollection(d.defaults.line.map(getItem)), + point: presetCollection(d.defaults.point.map(getItem)), + vertex: presetCollection(d.defaults.vertex.map(getItem)), + relation: presetCollection(d.defaults.relation.map(getItem)) }; } @@ -153,12 +154,12 @@ export function presets() { all.defaults = function(geometry, n) { var rec = recent.matchGeometry(geometry).collection.slice(0, 4), def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1); - return Collection(_.uniq(rec.concat(def).concat(all.item(geometry)))); + return presetCollection(_.uniq(rec.concat(def).concat(all.item(geometry)))); }; all.choose = function(preset) { if (!preset.isFallback()) { - recent = Collection(_.uniq([preset].concat(recent.collection))); + recent = presetCollection(_.uniq([preset].concat(recent.collection))); } return all; }; diff --git a/modules/presets/preset.js b/modules/presets/preset.js index 50e198671..ac9eaea0c 100644 --- a/modules/presets/preset.js +++ b/modules/presets/preset.js @@ -2,21 +2,25 @@ import _ from 'lodash'; import { t } from '../util/locale'; import { areaKeys } from '../core/context'; -export function Preset(id, preset, fields) { + +export function presetPreset(id, preset, fields) { preset = _.clone(preset); preset.id = id; preset.fields = (preset.fields || []).map(getFields); preset.geometry = (preset.geometry || []); + function getFields(f) { return fields[f]; } + preset.matchGeometry = function(geometry) { return preset.geometry.indexOf(geometry) >= 0; }; + var matchScore = preset.matchScore || 1; preset.matchScore = function(entity) { var tags = preset.tags, @@ -35,10 +39,12 @@ export function Preset(id, preset, fields) { return score; }; + preset.t = function(scope, options) { return t('presets.presets.' + id + '.' + scope, options); }; + var name = preset.name || ''; preset.name = function() { if (preset.suggestion) { @@ -49,15 +55,18 @@ export function Preset(id, preset, fields) { return preset.t('name', {'default': name}); }; + preset.terms = function() { return preset.t('terms', {'default': ''}).toLowerCase().trim().split(/\s*,+\s*/); }; + preset.isFallback = function() { var tagCount = Object.keys(preset.tags).length; return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area')); }; + preset.reference = function(geometry) { var key = Object.keys(preset.tags)[0], value = preset.tags[key]; @@ -78,6 +87,7 @@ export function Preset(id, preset, fields) { } }; + var removeTags = preset.removeTags || preset.tags; preset.removeTags = function(tags, geometry) { tags = _.omit(tags, _.keys(removeTags)); @@ -93,6 +103,7 @@ export function Preset(id, preset, fields) { return tags; }; + var applyTags = preset.addTags || preset.tags; preset.applyTags = function(tags, geometry) { var k; @@ -136,5 +147,6 @@ export function Preset(id, preset, fields) { return tags; }; + return preset; } diff --git a/modules/renderer/background.js b/modules/renderer/background.js index 9e1c25d0f..640418a7e 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -1,14 +1,15 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { Extent, metersToOffset, offsetToMeters} from '../geo/index'; -import { qsString, stringQs } from '../util/index'; -import { BackgroundSource } from './background_source'; -import { TileLayer } from './tile_layer'; +import { utilRebind } from '../util/rebind'; +import { geoExtent, geoMetersToOffset, geoOffsetToMeters} from '../geo/index'; +import { utilQsString, utilStringQs } from '../util/index'; +import { rendererBackgroundSource } from './background_source'; +import { rendererTileLayer } from './tile_layer'; -export function Background(context) { + +export function rendererBackground(context) { var dispatch = d3.dispatch('change'), - baseLayer = TileLayer(context).projection(context.projection), + baseLayer = rendererTileLayer(context).projection(context.projection), overlayLayers = [], backgroundSources; @@ -47,11 +48,11 @@ export function Background(context) { background.updateImagery = function() { var b = background.baseLayerSource(), o = overlayLayers.map(function (d) { return d.source().id; }).join(','), - meters = offsetToMeters(b.offset()), + meters = geoOffsetToMeters(b.offset()), epsilon = 0.01, x = +meters[0].toFixed(2), y = +meters[1].toFixed(2), - q = stringQs(location.hash.substring(1)); + q = utilStringQs(location.hash.substring(1)); var id = b.id; if (id === 'custom') { @@ -76,7 +77,7 @@ export function Background(context) { delete q.offset; } - location.replace('#' + qsString(q, true)); + location.replace('#' + utilQsString(q, true)); var imageryUsed = [b.imageryUsed()]; @@ -105,12 +106,14 @@ export function Background(context) { context.history().imageryUsed(imageryUsed); }; + background.sources = function(extent) { return backgroundSources.filter(function(source) { return source.intersects(extent); }); }; + background.dimensions = function(_) { if (!_) return; baseLayer.dimensions(_); @@ -120,6 +123,7 @@ export function Background(context) { }); }; + background.baseLayerSource = function(d) { if (!arguments.length) return baseLayer.source(); baseLayer.source(d); @@ -128,20 +132,24 @@ export function Background(context) { return background; }; + background.bing = function() { background.baseLayerSource(findSource('Bing')); }; + background.showsLayer = function(d) { return d === baseLayer.source() || (d.id === 'custom' && baseLayer.source().id === 'custom') || overlayLayers.some(function(l) { return l.source() === d; }); }; + background.overlayLayerSources = function() { return overlayLayers.map(function (l) { return l.source(); }); }; + background.toggleOverlayLayer = function(d) { var layer; @@ -165,6 +173,7 @@ export function Background(context) { background.updateImagery(); }; + background.nudge = function(d, zoom) { baseLayer.source().nudge(d, zoom); dispatch.call('change'); @@ -172,6 +181,7 @@ export function Background(context) { return background; }; + background.offset = function(d) { if (!arguments.length) return baseLayer.source().offset(); baseLayer.source().offset(d); @@ -180,24 +190,25 @@ export function Background(context) { return background; }; + background.load = function(imagery) { function parseMap(qmap) { if (!qmap) return false; var args = qmap.split('/').map(Number); if (args.length < 3 || args.some(isNaN)) return false; - return Extent([args[1], args[2]]); + return geoExtent([args[1], args[2]]); } - var q = stringQs(location.hash.substring(1)), + var q = utilStringQs(location.hash.substring(1)), chosen = q.background || q.layer, extent = parseMap(q.map), best; backgroundSources = imagery.map(function(source) { if (source.type === 'bing') { - return BackgroundSource.Bing(source, dispatch); + return rendererBackgroundSource.Bing(source, dispatch); } else { - return BackgroundSource(source); + return rendererBackgroundSource(source); } }); @@ -208,7 +219,7 @@ export function Background(context) { } if (chosen && chosen.indexOf('custom:') === 0) { - background.baseLayerSource(BackgroundSource.Custom(chosen.replace(/^custom:/, ''))); + background.baseLayerSource(rendererBackgroundSource.Custom(chosen.replace(/^custom:/, ''))); } else { background.baseLayerSource(findSource(chosen) || best || findSource('Bing') || backgroundSources[1] || backgroundSources[0]); } @@ -242,10 +253,11 @@ export function Background(context) { }); if (offset.length === 2) { - background.offset(metersToOffset(offset)); + background.offset(geoMetersToOffset(offset)); } } }; - return rebind(background, dispatch, 'on'); + + return utilRebind(background, dispatch, 'on'); } diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index b460a2c2a..28d66fcc5 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../util/locale'; -import { Extent, polygonIntersectsPolygon } from '../geo/index'; +import { geoExtent, geoPolygonIntersectsPolygon } from '../geo/index'; import { jsonpRequest } from '../util/jsonp_request'; -export function BackgroundSource(data) { + +export function rendererBackgroundSource(data) { var source = _.clone(data), offset = [0, 0], name = source.name, @@ -13,36 +14,43 @@ export function BackgroundSource(data) { source.scaleExtent = data.scaleExtent || [0, 20]; source.overzoom = data.overzoom !== false; + source.offset = function(_) { if (!arguments.length) return offset; offset = _; return source; }; + source.nudge = function(_, zoomlevel) { offset[0] += _[0] / Math.pow(2, zoomlevel); offset[1] += _[1] / Math.pow(2, zoomlevel); return source; }; + source.name = function() { return name; }; + source.best = function() { return best; }; + source.area = function() { if (!data.polygon) return Number.MAX_VALUE; // worldwide var area = d3.geoArea({ type: 'MultiPolygon', coordinates: [ data.polygon ] }); return isNaN(area) ? 0 : area; }; + source.imageryUsed = function() { return source.id || name; }; + source.url = function(coord) { return data.template .replace('{x}', coord[0]) @@ -67,34 +75,40 @@ export function BackgroundSource(data) { }); }; + source.intersects = function(extent) { extent = extent.polygon(); return !data.polygon || data.polygon.some(function(polygon) { - return polygonIntersectsPolygon(polygon, extent, true); + return geoPolygonIntersectsPolygon(polygon, extent, true); }); }; + source.validZoom = function(z) { return source.scaleExtent[0] <= z && (source.overzoom || source.scaleExtent[1] > z); }; + source.isLocatorOverlay = function() { return name === 'Locator Overlay'; }; + source.copyrightNotices = function() {}; + return source; } -BackgroundSource.Bing = function(data, dispatch) { + +rendererBackgroundSource.Bing = function(data, dispatch) { // http://msdn.microsoft.com/en-us/library/ff701716.aspx // http://msdn.microsoft.com/en-us/library/ff701701.aspx data.template = 'https://ecn.t{switch:0,1,2,3}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&mkt=en-gb&n=z'; - var bing = BackgroundSource(data), + var bing = rendererBackgroundSource(data), key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU', // Same as P2 and JOSM url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + key + '&jsonp={callback}', @@ -107,7 +121,7 @@ BackgroundSource.Bing = function(data, dispatch) { areas: provider.coverageAreas.map(function(area) { return { zoom: [area.zoomMin, area.zoomMax], - extent: Extent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) + extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) }; }) }; @@ -134,8 +148,9 @@ BackgroundSource.Bing = function(data, dispatch) { return bing; }; -BackgroundSource.None = function() { - var source = BackgroundSource({id: 'none', template: ''}); + +rendererBackgroundSource.None = function() { + var source = rendererBackgroundSource({ id: 'none', template: '' }); source.name = function() { return t('background.none'); @@ -152,8 +167,9 @@ BackgroundSource.None = function() { return source; }; -BackgroundSource.Custom = function(template) { - var source = BackgroundSource({id: 'custom', template: template}); + +rendererBackgroundSource.Custom = function(template) { + var source = rendererBackgroundSource({ id: 'custom', template: template }); source.name = function() { return t('background.custom'); diff --git a/modules/renderer/features.js b/modules/renderer/features.js index 041c24f75..ad640069c 100644 --- a/modules/renderer/features.js +++ b/modules/renderer/features.js @@ -1,9 +1,10 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { Entity } from '../core/index'; +import { utilRebind } from '../util/rebind'; +import { coreEntity } from '../core/index'; -export function Features(context) { + +export function rendererFeatures(context) { var traffic_roads = { 'motorway': true, 'motorway_link': true, @@ -55,12 +56,14 @@ export function Features(context) { _keys = [], _hidden = []; + function update() { _hidden = features.hidden(); dispatch.call('change'); dispatch.call('redraw'); } + function defineFeature(k, filter, max) { _keys.push(k); _features[k] = { @@ -170,14 +173,17 @@ export function Features(context) { function features() {} + features.features = function() { return _features; }; + features.keys = function() { return _keys; }; + features.enabled = function(k) { if (!arguments.length) { return _.filter(_keys, function(k) { return _features[k].enabled; }); @@ -185,6 +191,7 @@ export function Features(context) { return _features[k] && _features[k].enabled; }; + features.disabled = function(k) { if (!arguments.length) { return _.reject(_keys, function(k) { return _features[k].enabled; }); @@ -192,6 +199,7 @@ export function Features(context) { return _features[k] && !_features[k].enabled; }; + features.hidden = function(k) { if (!arguments.length) { return _.filter(_keys, function(k) { return _features[k].hidden(); }); @@ -199,6 +207,7 @@ export function Features(context) { return _features[k] && _features[k].hidden(); }; + features.autoHidden = function(k) { if (!arguments.length) { return _.filter(_keys, function(k) { return _features[k].autoHidden(); }); @@ -206,6 +215,7 @@ export function Features(context) { return _features[k] && _features[k].autoHidden(); }; + features.enable = function(k) { if (_features[k] && !_features[k].enabled) { _features[k].enable(); @@ -213,6 +223,7 @@ export function Features(context) { } }; + features.disable = function(k) { if (_features[k] && _features[k].enabled) { _features[k].disable(); @@ -220,6 +231,7 @@ export function Features(context) { } }; + features.toggle = function(k) { if (_features[k]) { (function(f) { return f.enabled ? f.disable() : f.enable(); }(_features[k])); @@ -227,11 +239,13 @@ export function Features(context) { } }; + features.resetStats = function() { _.each(_features, function(f) { f.count = 0; }); dispatch.call('change'); }; + features.gatherStats = function(d, resolver, dimensions) { var needsRedraw = false, type = _.groupBy(d, function(ent) { return ent.type; }), @@ -264,29 +278,34 @@ export function Features(context) { return needsRedraw; }; + features.stats = function() { _.each(_keys, function(k) { _stats[k] = _features[k].count; }); return _stats; }; + features.clear = function(d) { for (var i = 0; i < d.length; i++) { features.clearEntity(d[i]); } }; + features.clearEntity = function(entity) { - delete _cache[Entity.key(entity)]; + delete _cache[coreEntity.key(entity)]; }; + features.reset = function() { _cache = {}; }; + features.getMatches = function(entity, resolver, geometry) { if (geometry === 'vertex' || geometry === 'relation') return {}; - var ent = Entity.key(entity); + var ent = coreEntity.key(entity); if (!_cache[ent]) { _cache[ent] = {}; } @@ -312,7 +331,7 @@ export function Features(context) { if (entity.type === 'way') { var parents = features.getParents(entity, resolver, geometry); if (parents.length === 1 && parents[0].isMultipolygon()) { - var pkey = Entity.key(parents[0]); + var pkey = coreEntity.key(parents[0]); if (_cache[pkey] && _cache[pkey].matches) { matches = _.clone(_cache[pkey].matches); continue; @@ -331,10 +350,11 @@ export function Features(context) { return _cache[ent].matches; }; + features.getParents = function(entity, resolver, geometry) { if (geometry === 'point') return []; - var ent = Entity.key(entity); + var ent = coreEntity.key(entity); if (!_cache[ent]) { _cache[ent] = {}; } @@ -351,6 +371,7 @@ export function Features(context) { return _cache[ent].parents; }; + features.isHiddenFeature = function(entity, resolver, geometry) { if (!_hidden.length) return false; if (!entity.version) return false; @@ -363,6 +384,7 @@ export function Features(context) { return false; }; + features.isHiddenChild = function(entity, resolver, geometry) { if (!_hidden.length) return false; if (!entity.version || geometry === 'point') return false; @@ -378,6 +400,7 @@ export function Features(context) { return true; }; + features.hasHiddenConnections = function(entity, resolver) { if (!_hidden.length) return false; var childNodes, connections; @@ -400,6 +423,7 @@ export function Features(context) { }) : false; }; + features.isHidden = function(entity, resolver, geometry) { if (!_hidden.length) return false; if (!entity.version) return false; @@ -408,6 +432,7 @@ export function Features(context) { return fn(entity, resolver, geometry); }; + features.filter = function(d, resolver) { if (!_hidden.length) return d; @@ -421,5 +446,6 @@ export function Features(context) { return result; }; - return rebind(features, dispatch, 'on'); + + return utilRebind(features, dispatch, 'on'); } diff --git a/modules/renderer/index.js b/modules/renderer/index.js index 42ef8e0fb..ba7af8acb 100644 --- a/modules/renderer/index.js +++ b/modules/renderer/index.js @@ -1,5 +1,5 @@ -export { BackgroundSource } from './background_source'; -export { Background } from './background'; -export { Features } from './features'; -export { Map } from './map'; -export { TileLayer } from './tile_layer'; +export { rendererBackgroundSource } from './background_source'; +export { rendererBackground } from './background'; +export { rendererFeatures } from './features'; +export { rendererMap } from './map'; +export { rendererTileLayer } from './tile_layer'; diff --git a/modules/renderer/map.js b/modules/renderer/map.js index d6122cc36..9827d1f92 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -1,15 +1,33 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import { t } from '../util/locale'; -import { bindOnce } from '../util/bind_once'; -import { getDimensions } from '../util/dimensions'; -import { Areas, Labels, Layers, Lines, Midpoints, Points, Vertices } from '../svg/index'; -import { Extent } from '../geo/index'; -import { fastMouse, setTransform, functor } from '../util/index'; -import { flash } from '../ui/index'; -export function Map(context) { +import { utilRebind } from '../util/rebind'; +import { utilBindOnce } from '../util/bind_once'; +import { utilGetDimensions } from '../util/dimensions'; + +import { + svgAreas, + svgLabels, + svgLayers, + svgLines, + svgMidpoints, + svgPoints, + svgVertices +} from '../svg/index'; + +import { geoExtent } from '../geo/index'; + +import { + utilFastMouse, + utilSetTransform, + utilFunctor +} from '../util/index'; + +import { uiFlash } from '../ui/index'; + + +export function rendererMap(context) { var dimensions = [1, 1], dispatch = d3.dispatch('move', 'drawn'), @@ -19,13 +37,13 @@ export function Map(context) { transformStart = projection.transform(), transformed = false, minzoom = 0, - drawLayers = Layers(projection, context), - drawPoints = Points(projection, context), - drawVertices = Vertices(projection, context), - drawLines = Lines(projection), - drawAreas = Areas(projection, context), - drawMidpoints = Midpoints(projection, context), - drawLabels = Labels(projection, context), + drawLayers = svgLayers(projection, context), + drawPoints = svgPoints(projection, context), + drawVertices = svgVertices(projection, context), + drawLines = svgLines(projection), + drawAreas = svgAreas(projection, context), + drawMidpoints = svgMidpoints(projection, context), + drawLabels = svgLabels(projection, context), supersurface, wrapper, surface, @@ -64,7 +82,7 @@ export function Map(context) { supersurface = selection.append('div') .attr('id', 'supersurface') - .call(setTransform, 0, 0); + .call(utilSetTransform, 0, 0); // Need a wrapper div because Opera can't cope with an absolutely positioned // SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16 @@ -112,7 +130,7 @@ export function Map(context) { context.on('enter.map', function() { if (map.editable() && !transformed) { var all = context.intersects(map.extent()), - filter = functor(true), + filter = utilFunctor(true), graph = context.graph(); all = context.features().filter(all, graph); @@ -123,7 +141,7 @@ export function Map(context) { } }); - map.dimensions(getDimensions(selection)); + map.dimensions(utilGetDimensions(selection)); drawLabels.supersurface(supersurface); } @@ -168,7 +186,7 @@ export function Map(context) { } else { data = all; - filter = functor(true); + filter = utilFunctor(true); } } @@ -212,7 +230,7 @@ export function Map(context) { if (ktoz(eventTransform.k * 2 * Math.PI) < minzoom) { surface.interrupt(); - flash(context.container()) + uiFlash(context.container()) .select('.content') .text(t('cannot_zoom')); setZoom(context.minEditableZoom(), true); @@ -228,7 +246,7 @@ export function Map(context) { tY = (eventTransform.y / scale - transformStart.y) * scale; transformed = true; - setTransform(supersurface, tX, tY, scale); + utilSetTransform(supersurface, tX, tY, scale); queueRedraw(); dispatch.call('move', this, map); @@ -239,7 +257,7 @@ export function Map(context) { if (!transformed) return false; surface.selectAll('.radial-menu').interrupt().remove(); - setTransform(supersurface, 0, 0); + utilSetTransform(supersurface, 0, 0); transformed = false; return true; } @@ -427,7 +445,7 @@ export function Map(context) { drawLayers.dimensions(dimensions); context.background().dimensions(dimensions); projection.clipExtent([[0, 0], dimensions]); - mouse = fastMouse(supersurface.node()); + mouse = utilFastMouse(supersurface.node()); setCenter(center); return redraw(); @@ -469,7 +487,7 @@ export function Map(context) { if (z2 < minzoom) { surface.interrupt(); - flash(context.container()) + uiFlash(context.container()) .select('.content') .text(t('cannot_zoom')); z2 = context.minEditableZoom(); @@ -520,7 +538,7 @@ export function Map(context) { map.startEase = function() { - bindOnce(surface, 'mousedown.ease', function() { + utilBindOnce(surface, 'mousedown.ease', function() { map.cancelEase(); }); return map; @@ -535,10 +553,10 @@ export function Map(context) { map.extent = function(_) { if (!arguments.length) { - return new Extent(projection.invert([0, dimensions[1]]), + return new geoExtent(projection.invert([0, dimensions[1]]), projection.invert([dimensions[0], 0])); } else { - var extent = Extent(_); + var extent = geoExtent(_); map.centerZoom(extent.center(), map.extentZoom(extent)); } }; @@ -547,10 +565,10 @@ export function Map(context) { map.trimmedExtent = function(_) { if (!arguments.length) { var headerY = 60, footerY = 30, pad = 10; - return new Extent(projection.invert([pad, dimensions[1] - footerY - pad]), - projection.invert([dimensions[0] - pad, headerY + pad])); + return new geoExtent(projection.invert([pad, dimensions[1] - footerY - pad]), + projection.invert([dimensions[0] - pad, headerY + pad])); } else { - var extent = Extent(_); + var extent = geoExtent(_); map.centerZoom(extent.center(), map.trimmedExtentZoom(extent)); } }; @@ -572,14 +590,14 @@ export function Map(context) { map.extentZoom = function(_) { - return calcZoom(Extent(_), dimensions); + return calcZoom(geoExtent(_), dimensions); }; map.trimmedExtentZoom = function(_) { var trimY = 120, trimX = 40, trimmed = [dimensions[0] - trimX, dimensions[1] - trimY]; - return calcZoom(Extent(_), trimmed); + return calcZoom(geoExtent(_), trimmed); }; @@ -597,5 +615,6 @@ export function Map(context) { map.layers = drawLayers; - return rebind(map, dispatch, 'on'); + + return utilRebind(map, dispatch, 'on'); } diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js index cf343164d..8f17930d9 100644 --- a/modules/renderer/tile_layer.js +++ b/modules/renderer/tile_layer.js @@ -1,17 +1,18 @@ import * as d3 from 'd3'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { prefixCSSProperty } from '../util/index'; -import { BackgroundSource } from './background_source.js'; +import { utilPrefixCSSProperty } from '../util/index'; +import { rendererBackgroundSource } from './background_source.js'; -export function TileLayer(context) { + +export function rendererTileLayer(context) { var tileSize = 256, tile = d3geoTile(), projection, cache = {}, tileOrigin, z, - transformProp = prefixCSSProperty('Transform'), - source = BackgroundSource.None(); + transformProp = utilPrefixCSSProperty('Transform'), + source = rendererBackgroundSource.None(); // blacklist overlay tiles around Null Island.. diff --git a/modules/services/index.js b/modules/services/index.js index b549af20d..2e9d38d30 100644 --- a/modules/services/index.js +++ b/modules/services/index.js @@ -1,7 +1,13 @@ -import * as mapillary from './mapillary'; -import * as nominatim from './nominatim'; -import * as taginfo from './taginfo'; -import * as wikidata from './wikidata'; -import * as wikipedia from './wikipedia'; +import * as serviceMapillary from './mapillary'; +import * as serviceNominatim from './nominatim'; +import * as serviceTaginfo from './taginfo'; +import * as serviceWikidata from './wikidata'; +import * as serviceWikipedia from './wikipedia'; -export { mapillary, taginfo, nominatim, wikidata, wikipedia}; +export { + serviceMapillary, + serviceTaginfo, + serviceNominatim, + serviceWikidata, + serviceWikipedia +}; diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index 38c23b08e..657bba71d 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -2,12 +2,12 @@ import * as d3 from 'd3'; import _ from 'lodash'; import rbush from 'rbush'; -import { rebind } from '../util/rebind'; +import { utilRebind } from '../util/rebind'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { Detect } from '../util/detect'; -import { Extent } from '../geo/index'; -import { Icon } from '../svg/index'; -import { qsString } from '../util/index'; +import { utilDetect } from '../util/detect'; +import { geoExtent } from '../geo/index'; +import { svgIcon } from '../svg/index'; +import { utilQsString } from '../util/index'; var mapillary = {}, @@ -20,6 +20,7 @@ var mapillary = {}, tileZoom = 14, dispatch = d3.dispatch('loadedImages', 'loadedSigns'); + function loadSignStyles(context) { d3.select('head').selectAll('#traffico') .data([0]) @@ -30,6 +31,7 @@ function loadSignStyles(context) { .attr('href', context.asset('traffico/stylesheets/traffico.css')); } + function loadSignDefs(context) { if (mapillary.sign_defs) return; mapillary.sign_defs = {}; @@ -43,6 +45,7 @@ function loadSignDefs(context) { }); } + function loadViewer() { // mapillary-wrap var wrap = d3.select('#content').selectAll('.mapillary-wrap') @@ -59,7 +62,7 @@ function loadViewer() { .attr('class', 'thumb-hide') .on('click', function () { mapillary.hideViewer(); }) .append('div') - .call(Icon('#icon-close')); + .call(svgIcon('#icon-close')); enter .append('div') @@ -85,6 +88,7 @@ function loadViewer() { .attr('src', viewerjs); } + function initViewer(imageKey, context) { function nodeChanged(d) { @@ -117,10 +121,12 @@ function initViewer(imageKey, context) { } } + function abortRequest(i) { i.abort(); } + function nearNullIsland(x, y, z) { if (z >= 7) { var center = Math.pow(2, z - 1), @@ -132,6 +138,7 @@ function nearNullIsland(x, y, z) { return false; } + function getTiles(projection, dimensions) { var s = projection.scale() * 2 * Math.PI, z = Math.max(Math.log(s) / Math.log(2) - 8, 0), @@ -151,13 +158,15 @@ function getTiles(projection, dimensions) { return { id: tile.toString(), - extent: Extent( + extent: geoExtent( projection.invert([x, y + ts]), - projection.invert([x + ts, y])) + projection.invert([x + ts, y]) + ) }; }); } + function loadTiles(which, url, projection, dimensions) { var tiles = getTiles(projection, dimensions).filter(function(t) { var xyz = t.id.split(','); @@ -175,6 +184,7 @@ function loadTiles(which, url, projection, dimensions) { }); } + function loadTilePage(which, url, tile, page) { var cache = mapillary.cache[which], id = tile.id + ',' + String(page), @@ -183,7 +193,7 @@ function loadTilePage(which, url, tile, page) { if (cache.loaded[id] || cache.inflight[id]) return; cache.inflight[id] = d3.json(url + - qsString({ + utilQsString({ geojson: 'true', limit: maxResults, page: page, @@ -223,6 +233,7 @@ function loadTilePage(which, url, tile, page) { ); } + // partition viewport into `psize` x `psize` regions function partitionViewport(psize, projection, dimensions) { psize = psize || 16; @@ -235,13 +246,14 @@ function partitionViewport(psize, projection, dimensions) { var min = [x, y + psize], max = [x + psize, y]; partitions.push( - Extent(projection.invert(min), projection.invert(max))); + geoExtent(projection.invert(min), projection.invert(max))); }); }); return partitions; } + // no more than `limit` results per partition. function searchLimited(psize, limit, projection, dimensions, rtree) { limit = limit || 3; @@ -254,11 +266,13 @@ function searchLimited(psize, limit, projection, dimensions, rtree) { }))); } + // this function is only used by test cases export function getMapillary() { return mapillary; } + export function init() { mapillary.loadImages = function(projection, dimensions) { @@ -293,7 +307,7 @@ export function init() { mapillary.signsSupported = function() { - var detected = Detect(); + var detected = utilDetect(); return (!(detected.ie || detected.browser.toLowerCase() === 'safari')); }; @@ -423,7 +437,7 @@ export function init() { } - mapillary.event = rebind(mapillary, dispatch, 'on'); + mapillary.event = utilRebind(mapillary, dispatch, 'on'); return mapillary; } diff --git a/modules/services/nominatim.js b/modules/services/nominatim.js index 70a3e5e3c..950be8ab3 100644 --- a/modules/services/nominatim.js +++ b/modules/services/nominatim.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; import rbush from 'rbush'; -import { Extent } from '../geo/index'; -import { qsString } from '../util/index'; +import { geoExtent } from '../geo/index'; +import { utilQsString } from '../util/index'; + var endpoint, cache; @@ -12,18 +13,22 @@ export function init() { } } + export function reset() { cache = rbush(); } + export function countryCode(location, callback) { - var countryCodes = cache.search({ minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] }); + var countryCodes = cache.search({ + minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] + }); if (countryCodes.length > 0) return callback(null, countryCodes[0].data); d3.json(endpoint + - qsString({ + utilQsString({ format: 'json', addressdetails: 1, lat: location[1], @@ -34,7 +39,7 @@ export function countryCode(location, callback) { else if (result && result.error) return callback(result.error); - var extent = Extent(location).padByMeters(1000); + var extent = geoExtent(location).padByMeters(1000); cache.insert(Object.assign(extent.bbox(), { data: result.address.country_code })); diff --git a/modules/services/taginfo.js b/modules/services/taginfo.js index 0133bb817..72d7f0de4 100644 --- a/modules/services/taginfo.js +++ b/modules/services/taginfo.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { qsString } from '../util/index'; +import { utilQsString } from '../util/index'; + var taginfo = {}, endpoint = 'https://taginfo.openstreetmap.org/api/4/', @@ -31,6 +32,7 @@ var taginfo = {}, relation: 'count_relation_members_fraction' }; + function sets(parameters, n, o) { if (parameters.geometry && o[parameters.geometry]) { parameters[n] = o[parameters.geometry]; @@ -38,22 +40,27 @@ function sets(parameters, n, o) { return parameters; } + function setFilter(parameters) { return sets(parameters, 'filter', tag_filters); } + function setSort(parameters) { return sets(parameters, 'sortname', tag_sorts); } + function setSortMembers(parameters) { return sets(parameters, 'sortname', tag_sort_members); } + function clean(parameters) { return _.omit(parameters, 'geometry', 'debounce'); } + function filterKeys(type) { var count_type = type ? 'count_' + type : 'count_all'; return function(d) { @@ -61,12 +68,14 @@ function filterKeys(type) { }; } + function filterMultikeys() { return function(d) { return (d.key.match(/:/g) || []).length === 1; // exactly one ':' }; } + function filterValues(allowUpperCase) { return function(d) { if (d.value.match(/[;,]/) !== null) return false; // exclude some punctuation @@ -75,6 +84,7 @@ function filterValues(allowUpperCase) { }; } + function filterRoles(geometry) { return function(d) { if (d.role === '') return false; // exclude empty role @@ -83,6 +93,7 @@ function filterRoles(geometry) { }; } + function valKey(d) { return { value: d.key, @@ -90,6 +101,7 @@ function valKey(d) { }; } + function valKeyDescription(d) { return { value: d.value, @@ -97,6 +109,7 @@ function valKeyDescription(d) { }; } + function roleKey(d) { return { value: d.role, @@ -104,6 +117,7 @@ function roleKey(d) { }; } + // sort keys with ':' lower than keys without ':' function sortKeys(a, b) { return (a.key.indexOf(':') === -1 && b.key.indexOf(':') !== -1) ? -1 @@ -111,8 +125,10 @@ function sortKeys(a, b) { : 0; } + var debounced = _.debounce(d3.json, 100, true); + function request(url, debounce, callback) { var cache = taginfo.cache; @@ -130,12 +146,13 @@ function request(url, debounce, callback) { } } + export function init() { taginfo.keys = function(parameters, callback) { var debounce = parameters.debounce; parameters = clean(setSort(parameters)); request(endpoint + 'keys/all?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 10, sortname: 'count_all', sortorder: 'desc', @@ -151,7 +168,7 @@ export function init() { var debounce = parameters.debounce; parameters = clean(setSort(parameters)); request(endpoint + 'keys/all?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 25, sortname: 'count_all', sortorder: 'desc', @@ -167,7 +184,7 @@ export function init() { var debounce = parameters.debounce; parameters = clean(setSort(setFilter(parameters))); request(endpoint + 'key/values?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 25, sortname: 'count_all', sortorder: 'desc', @@ -184,7 +201,7 @@ export function init() { var geometry = parameters.geometry; parameters = clean(setSortMembers(parameters)); request(endpoint + 'relation/roles?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 25, sortname: 'count_all_members', sortorder: 'desc', @@ -204,7 +221,7 @@ export function init() { if (parameters.value) path = 'tag/wiki_pages?'; else if (parameters.rtype) path = 'relation/wiki_pages?'; - request(endpoint + path + qsString(parameters), debounce, function(err, d) { + request(endpoint + path + utilQsString(parameters), debounce, function(err, d) { if (err) return callback(err); callback(null, d.data); }); diff --git a/modules/services/wikidata.js b/modules/services/wikidata.js index 08932a230..a1dfbe9cf 100644 --- a/modules/services/wikidata.js +++ b/modules/services/wikidata.js @@ -1,5 +1,6 @@ import { jsonpRequest } from '../util/jsonp_request'; -import { qsString } from '../util/index'; +import { utilQsString } from '../util/index'; + var wikidata = {}, endpoint = 'https://www.wikidata.org/w/api.php?'; @@ -16,7 +17,7 @@ export function init() { } lang = lang || 'en'; - jsonpRequest(endpoint + qsString({ + jsonpRequest(endpoint + utilQsString({ action: 'wbgetentities', format: 'json', sites: lang.replace(/-/g, '_') + 'wiki', diff --git a/modules/services/wikipedia.js b/modules/services/wikipedia.js index 87dcb87d0..541800fb2 100644 --- a/modules/services/wikipedia.js +++ b/modules/services/wikipedia.js @@ -1,5 +1,6 @@ import { jsonpRequest } from '../util/jsonp_request'; -import { qsString } from '../util/index'; +import { utilQsString } from '../util/index'; + var wikipedia = {}, endpoint = 'https://en.wikipedia.org/w/api.php?'; @@ -14,7 +15,7 @@ export function init() { lang = lang || 'en'; jsonpRequest(endpoint.replace('en', lang) + - qsString({ + utilQsString({ action: 'query', list: 'search', srlimit: '10', @@ -42,7 +43,7 @@ export function init() { lang = lang || 'en'; jsonpRequest(endpoint.replace('en', lang) + - qsString({ + utilQsString({ action: 'opensearch', namespace: 0, suggest: '', @@ -67,7 +68,7 @@ export function init() { } jsonpRequest(endpoint.replace('en', lang) + - qsString({ + utilQsString({ action: 'query', prop: 'langlinks', format: 'json', diff --git a/modules/svg/areas.js b/modules/svg/areas.js index e99eff835..9d39c35ee 100644 --- a/modules/svg/areas.js +++ b/modules/svg/areas.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Path, TagClasses } from './index'; -import { Entity } from '../core/index'; -import { isSimpleMultipolygonOuterMember } from '../geo/index'; +import { svgPath, svgTagClasses } from './index'; +import { coreEntity } from '../core/index'; +import { geoIsSimpleMultipolygonOuterMember } from '../geo/index'; -export function Areas(projection, context) { + +export function svgAreas(projection, context) { // Patterns only work in Firefox when set directly on element. // (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632) var patterns = { @@ -23,6 +24,7 @@ export function Areas(projection, context) { var patternKeys = ['landuse', 'natural', 'amenity']; + function setPattern(d) { for (var i = 0; i < patternKeys.length; i++) { if (patterns.hasOwnProperty(d.tags[patternKeys[i]])) { @@ -33,8 +35,9 @@ export function Areas(projection, context) { this.style.fill = this.style.stroke = ''; } + return function drawAreas(selection, graph, entities, filter) { - var path = Path(projection, graph, true), + var path = svgPath(projection, graph, true), areas = {}, multipolygon; @@ -42,7 +45,7 @@ export function Areas(projection, context) { var entity = entities[i]; if (entity.geometry(graph) !== 'area') continue; - multipolygon = isSimpleMultipolygonOuterMember(entity, graph); + multipolygon = geoIsSimpleMultipolygonOuterMember(entity, graph); if (multipolygon) { areas[multipolygon.id] = { entity: multipolygon.mergeTags(entity.tags), @@ -73,7 +76,7 @@ export function Areas(projection, context) { var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath') .filter(filter) - .data(data.clip, Entity.key); + .data(data.clip, coreEntity.key); clipPaths.exit() .remove(); @@ -105,7 +108,7 @@ export function Areas(projection, context) { var paths = areagroup .selectAll('path') .filter(filter) - .data(function(layer) { return data[layer]; }, Entity.key); + .data(function(layer) { return data[layer]; }, coreEntity.key); // Remove exiting areas first, so they aren't included in the `fills` // array used for sorting below (https://github.com/openstreetmap/iD/issues/1903). @@ -137,7 +140,7 @@ export function Areas(projection, context) { setPattern.apply(this, arguments); } }) - .call(TagClasses()) + .call(svgTagClasses()) .attr('d', path); }; } diff --git a/modules/svg/debug.js b/modules/svg/debug.js index df9b135cb..1953da221 100644 --- a/modules/svg/debug.js +++ b/modules/svg/debug.js @@ -1,12 +1,13 @@ import * as d3 from 'd3'; -import { polygonIntersectsPolygon } from '../geo/index'; +import { geoPolygonIntersectsPolygon } from '../geo/index'; import { - imperial as imperialData, - driveLeft as driveLeftData, - imagery as imageryData + dataImperial as imperialData, + dataDriveLeft as driveLeftData, + dataImagery as imageryData } from '../../data/index'; -export function Debug(projection, context) { + +export function svgDebug(projection, context) { function multipolygons(imagery) { return imagery.map(function(data) { @@ -82,10 +83,10 @@ export function Debug(projection, context) { var extent = context.map().extent(), - availableImagery = showsImagery && multipolygons(imageryData.filter(function(source) { + availableImagery = showsImagery && multipolygons(dataImagery.filter(function(source) { if (!source.polygon) return false; return source.polygon.some(function(polygon) { - return polygonIntersectsPolygon(polygon, extent, true); + return geoPolygonIntersectsPolygon(polygon, extent, true); }); })); @@ -100,10 +101,9 @@ export function Debug(projection, context) { .attr('class', 'debug-imagery debug orange'); - var imperial = layer .selectAll('path.debug-imperial') - .data(showsImperial ? [imperialData] : []); + .data(showsImperial ? [dataImperial] : []); imperial.exit() .remove(); @@ -113,10 +113,9 @@ export function Debug(projection, context) { .attr('class', 'debug-imperial debug cyan'); - var driveLeft = layer .selectAll('path.debug-drive-left') - .data(showsDriveLeft ? [driveLeftData] : []); + .data(showsDriveLeft ? [dataDriveLeft] : []); driveLeft.exit() .remove(); @@ -146,5 +145,6 @@ export function Debug(projection, context) { } }; + return drawDebug; } diff --git a/modules/svg/defs.js b/modules/svg/defs.js index 8591210ee..ef9eced17 100644 --- a/modules/svg/defs.js +++ b/modules/svg/defs.js @@ -4,22 +4,23 @@ import * as d3 from 'd3'; A standalone SVG element that contains only a `defs` sub-element. To be used once globally, since defs IDs must be unique within a document. */ -export function Defs(context) { +export function svgDefs(context) { function SVGSpriteDefinition(id, href) { return function(defs) { d3.request(href) - .mimeType('image/svg+xml') - .response(function(xhr) { return xhr.responseXML; }) + .mimeType('image/svg+xml') + .response(function(xhr) { return xhr.responseXML; }) .get(function(err, svg) { if (err) return; defs.node().appendChild( - d3.select(svg.documentElement).attr('id', id).node() - ); - }); + d3.select(svg.documentElement).attr('id', id).node() + ); + }); }; } + return function drawDefs(selection) { var defs = selection.append('defs'); diff --git a/modules/svg/gpx.js b/modules/svg/gpx.js index 29c63e2a7..320fe1a93 100644 --- a/modules/svg/gpx.js +++ b/modules/svg/gpx.js @@ -1,19 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, polygonIntersectsPolygon } from '../geo/index'; -import { Detect } from '../util/detect'; +import { geoExtent, geoPolygonIntersectsPolygon } from '../geo/index'; +import { utilDetect } from '../util/detect'; import toGeoJSON from 'togeojson'; -export function Gpx(projection, context, dispatch) { + +export function svgGpx(projection, context, dispatch) { var showLabels = true, layer; function init() { - if (Gpx.initialized) return; // run once + if (svgGpx.initialized) return; // run once - Gpx.geojson = {}; - Gpx.enabled = true; + svgGpx.geojson = {}; + svgGpx.enabled = true; function over() { d3.event.stopPropagation(); @@ -26,20 +27,20 @@ export function Gpx(projection, context, dispatch) { .on('drop.localgpx', function() { d3.event.stopPropagation(); d3.event.preventDefault(); - if (!Detect().filedrop) return; + if (!utilDetect().filedrop) return; drawGpx.files(d3.event.dataTransfer.files); }) .on('dragenter.localgpx', over) .on('dragexit.localgpx', over) .on('dragover.localgpx', over); - Gpx.initialized = true; + svgGpx.initialized = true; } function drawGpx(selection) { - var geojson = Gpx.geojson, - enabled = Gpx.enabled; + var geojson = svgGpx.geojson, + enabled = svgGpx.enabled; layer = selection.selectAll('.layer-gpx') .data(enabled ? [0] : []); @@ -170,8 +171,8 @@ export function Gpx(projection, context, dispatch) { return _.union(coords, feature.geometry.type === 'Point' ? [c] : c); }, []); - if (!polygonIntersectsPolygon(viewport, coords, true)) { - var extent = Extent(d3.geoBounds(geojson)); + if (!geoPolygonIntersectsPolygon(viewport, coords, true)) { + var extent = geoExtent(d3.geoBounds(geojson)); map.centerZoom(extent.center(), map.trimmedExtentZoom(extent)); } diff --git a/modules/svg/icon.js b/modules/svg/icon.js index 97f8a1f34..8d287ad3a 100644 --- a/modules/svg/icon.js +++ b/modules/svg/icon.js @@ -1,4 +1,4 @@ -export function Icon(name, svgklass, useklass) { +export function svgIcon(name, svgklass, useklass) { return function drawIcon(selection) { selection.selectAll('svg') .data([0]) diff --git a/modules/svg/index.js b/modules/svg/index.js index 27d16672f..9b48ba93d 100644 --- a/modules/svg/index.js +++ b/modules/svg/index.js @@ -1,20 +1,20 @@ -export { Areas } from './areas.js'; -export { Debug } from './debug.js'; -export { Defs } from './defs.js'; -export { Gpx } from './gpx.js'; -export { Icon } from './icon.js'; -export { Labels } from './labels.js'; -export { Layers } from './layers.js'; -export { Lines } from './lines.js'; -export { MapillaryImages } from './mapillary_images.js'; -export { MapillarySigns } from './mapillary_signs.js'; -export { Midpoints } from './midpoints.js'; -export { OneWaySegments } from './one_way_segments.js'; -export { Osm } from './osm.js'; -export { Path } from './path.js'; -export { PointTransform } from './point_transform.js'; -export { Points } from './points.js'; -export { RelationMemberTags } from './relation_member_tags.js'; -export { TagClasses } from './tag_classes.js'; -export { Turns } from './turns.js'; -export { Vertices } from './vertices.js'; +export { svgAreas } from './areas.js'; +export { svgDebug } from './debug.js'; +export { svgDefs } from './defs.js'; +export { svgGpx } from './gpx.js'; +export { svgIcon } from './icon.js'; +export { svgLabels } from './labels.js'; +export { svgLayers } from './layers.js'; +export { svgLines } from './lines.js'; +export { svgMapillaryImages } from './mapillary_images.js'; +export { svgMapillarySigns } from './mapillary_signs.js'; +export { svgMidpoints } from './midpoints.js'; +export { svgOneWaySegments } from './one_way_segments.js'; +export { svgOsm } from './osm.js'; +export { svgPath } from './path.js'; +export { svgPointTransform } from './point_transform.js'; +export { svgPoints } from './points.js'; +export { svgRelationMemberTags } from './relation_member_tags.js'; +export { svgTagClasses } from './tag_classes.js'; +export { svgTurns } from './turns.js'; +export { svgVertices } from './vertices.js'; diff --git a/modules/svg/labels.js b/modules/svg/labels.js index 60a592239..89d156de3 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -1,12 +1,12 @@ import * as d3 from 'd3'; import _ from 'lodash'; import rbush from 'rbush'; -import { displayName, getStyle } from '../util/index'; -import { Entity } from '../core/index'; -import { pathLength } from '../geo/index'; +import { utilDisplayName, utilGetStyle } from '../util/index'; +import { coreEntity } from '../core/index'; +import { geoPathLength } from '../geo/index'; -export function Labels(projection, context) { +export function svgLabels(projection, context) { var path = d3.geoPath().projection(projection); // Replace with dict and iterate over entities tags instead? @@ -41,11 +41,11 @@ export function Labels(projection, context) { var default_size = 12; var font_sizes = label_stack.map(function(d) { - var style = getStyle('text.' + d[0] + '.tag-' + d[1]), + var style = utilGetStyle('text.' + d[0] + '.tag-' + d[1]), m = style && style.cssText.match('font-size: ([0-9]{1,2})px;'); if (m) return parseInt(m[1], 10); - style = getStyle('text.' + d[0]); + style = utilGetStyle('text.' + d[0]); m = style && style.cssText.match('font-size: ([0-9]{1,2})px;'); if (m) return parseInt(m[1], 10); @@ -104,7 +104,7 @@ export function Labels(projection, context) { function drawLinePaths(selection, entities, filter, classes, labels) { var paths = selection.selectAll('path') .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); paths.exit() .remove(); @@ -122,7 +122,7 @@ export function Labels(projection, context) { function drawLineLabels(selection, entities, filter, classes, labels) { var texts = selection.selectAll('text.' + classes) .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); texts.exit() .remove(); @@ -137,17 +137,17 @@ export function Labels(projection, context) { texts.selectAll('.textpath') .filter(filter) - .data(entities, Entity.key) + .data(entities, coreEntity.key) .attr('startOffset', '50%') .attr('xlink:href', function(d) { return '#labelpath-' + d.id; }) - .text(displayName); + .text(utilDisplayName); } function drawPointLabels(selection, entities, filter, classes, labels) { var texts = selection.selectAll('text.' + classes) .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); texts.exit() .remove(); @@ -163,9 +163,9 @@ export function Labels(projection, context) { .attr('x', get(labels, 'x')) .attr('y', get(labels, 'y')) .style('text-anchor', get(labels, 'textAnchor')) - .text(displayName) + .text(utilDisplayName) .each(function(d, i) { - textWidth(displayName(d), labels[i].height, this); + textWidth(utilDisplayName(d), labels[i].height, this); }); } @@ -184,7 +184,7 @@ export function Labels(projection, context) { function drawAreaIcons(selection, entities, filter, classes, labels) { var icons = selection.selectAll('use') .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); icons.exit() .remove(); @@ -303,7 +303,7 @@ export function Labels(projection, context) { var preset = geometry === 'area' && context.presets().match(entity, graph), icon = preset && !blacklisted(preset) && preset.icon; - if (!icon && !displayName(entity)) + if (!icon && !utilDisplayName(entity)) continue; for (k = 0; k < label_stack.length; k++) { @@ -331,7 +331,7 @@ export function Labels(projection, context) { var font_size = font_sizes[k]; for (i = 0; i < labelable[k].length; i++) { entity = labelable[k][i]; - var name = displayName(entity), + var name = utilDisplayName(entity), width = name && textWidth(name, font_size), p; if (entity.geometry(graph) === 'point') { @@ -368,7 +368,7 @@ export function Labels(projection, context) { function getLineLabel(entity, width, height) { var nodes = _.map(graph.childNodes(entity), 'loc').map(projection), - length = pathLength(nodes); + length = geoPathLength(nodes); if (length < width + 20) return; for (var i = 0; i < lineOffsets.length; i++) { diff --git a/modules/svg/layers.js b/modules/svg/layers.js index 2bd5fd58f..98b5ba901 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -1,23 +1,23 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { getDimensions, setDimensions } from '../util/dimensions'; -import { Debug } from './debug'; -import { Gpx } from './gpx'; -import { MapillaryImages } from './mapillary_images'; -import { MapillarySigns } from './mapillary_signs'; -import { Osm } from './osm'; +import { utilRebind } from '../util/rebind'; +import { utilGetDimensions, utilSetDimensions } from '../util/dimensions'; +import { svgDebug } from './debug'; +import { svgGpx } from './gpx'; +import { svgMapillaryImages } from './mapillary_images'; +import { svgMapillarySigns } from './mapillary_signs'; +import { svgOsm } from './osm'; -export function Layers(projection, context) { +export function svgLayers(projection, context) { var dispatch = d3.dispatch('change'), svg = d3.select(null), layers = [ - { id: 'osm', layer: Osm(projection, context, dispatch) }, - { id: 'gpx', layer: Gpx(projection, context, dispatch) }, - { id: 'mapillary-images', layer: MapillaryImages(projection, context, dispatch) }, - { id: 'mapillary-signs', layer: MapillarySigns(projection, context, dispatch) }, - { id: 'debug', layer: Debug(projection, context, dispatch) } + { id: 'osm', layer: svgOsm(projection, context, dispatch) }, + { id: 'gpx', layer: svgGpx(projection, context, dispatch) }, + { id: 'mapillary-images', layer: svgMapillaryImages(projection, context, dispatch) }, + { id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) }, + { id: 'debug', layer: svgDebug(projection, context, dispatch) } ]; @@ -92,8 +92,8 @@ export function Layers(projection, context) { drawLayers.dimensions = function(_) { - if (!arguments.length) return getDimensions(svg); - setDimensions(svg, _); + if (!arguments.length) return utilGetDimensions(svg); + utilSetDimensions(svg, _); layers.forEach(function(obj) { if (obj.layer.dimensions) { obj.layer.dimensions(_); @@ -103,5 +103,5 @@ export function Layers(projection, context) { }; - return rebind(drawLayers, dispatch, 'on'); + return utilRebind(drawLayers, dispatch, 'on'); } diff --git a/modules/svg/lines.js b/modules/svg/lines.js index 76f3511c0..dcf4b2da3 100644 --- a/modules/svg/lines.js +++ b/modules/svg/lines.js @@ -1,11 +1,18 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { OneWaySegments, Path, RelationMemberTags, TagClasses } from './index'; -import { Detect } from '../util/detect'; -import { Entity } from '../core/index'; -import { simpleMultipolygonOuterMember } from '../geo/index'; +import { + svgOneWaySegments, + svgPath, + svgRelationMemberTags, + svgTagClasses +} from './index'; -export function Lines(projection) { +import { utilDetect } from '../util/detect'; +import { coreEntity } from '../core/index'; +import { geoSimpleMultipolygonOuterMember } from '../geo/index'; + + +export function svgLines(projection) { var highway_stack = { motorway: 0, @@ -24,19 +31,19 @@ export function Lines(projection) { function waystack(a, b) { var as = 0, bs = 0; - if (a.tags.highway) { as -= highway_stack[a.tags.highway]; } if (b.tags.highway) { bs -= highway_stack[b.tags.highway]; } return as - bs; } + return function drawLines(selection, graph, entities, filter) { var ways = [], pathdata = {}, onewaydata = {}, - getPath = Path(projection, graph); + getPath = svgPath(projection, graph); for (var i = 0; i < entities.length; i++) { var entity = entities[i], - outer = simpleMultipolygonOuterMember(entity, graph); + outer = geoSimpleMultipolygonOuterMember(entity, graph); if (outer) { ways.push(entity.mergeTags(outer.tags)); } else if (entity.geometry(graph) === 'line') { @@ -83,22 +90,22 @@ export function Lines(projection) { .filter(filter) .data( function() { return pathdata[this.parentNode.__data__] || []; }, - Entity.key + coreEntity.key ); lines.exit() .remove(); // Optimization: call simple TagClasses only on enter selection. This - // works because Entity.key is defined to include the entity v attribute. + // works because coreEntity.key is defined to include the entity v attribute. lines.enter() .append('path') .attr('class', function(d) { return 'way line ' + this.parentNode.__data__ + ' ' + d.id; }) - .call(TagClasses()) + .call(svgTagClasses()) .merge(lines) .sort(waystack) .attr('d', getPath) - .call(TagClasses().tags(RelationMemberTags(graph))); + .call(svgTagClasses().tags(svgRelationMemberTags(graph))); var onewaygroup = layergroup @@ -129,7 +136,7 @@ export function Lines(projection) { .merge(oneways) .attr('d', function(d) { return d.d; }); - if (Detect().ie) { + if (utilDetect().ie) { oneways.each(function() { this.parentNode.insertBefore(this, this); }); } }; diff --git a/modules/svg/mapillary_images.js b/modules/svg/mapillary_images.js index c9fbf713a..7e716186c 100644 --- a/modules/svg/mapillary_images.js +++ b/modules/svg/mapillary_images.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { PointTransform } from './point_transform'; -import { getDimensions, setDimensions } from '../util/dimensions'; -import { mapillary as mapillaryService } from '../services/index'; +import { svgPointTransform } from './point_transform'; +import { utilGetDimensions, utilSetDimensions } from '../util/dimensions'; +import { serviceMapillary } from '../services/index'; -export function MapillaryImages(projection, context, dispatch) { + +export function svgMapillaryImages(projection, context, dispatch) { var debouncedRedraw = _.debounce(function () { dispatch.call('change'); }, 1000), minZoom = 12, layer = d3.select(null), @@ -12,17 +13,17 @@ export function MapillaryImages(projection, context, dispatch) { function init() { - if (MapillaryImages.initialized) return; // run once - MapillaryImages.enabled = false; - MapillaryImages.initialized = true; + if (svgMapillaryImages.initialized) return; // run once + svgMapillaryImages.enabled = false; + svgMapillaryImages.initialized = true; } function getMapillary() { - if (mapillaryService && !_mapillary) { - _mapillary = mapillaryService.init(); + if (serviceMapillary && !_mapillary) { + _mapillary = serviceMapillary.init(); _mapillary.event.on('loadedImages', debouncedRedraw); - } else if (!mapillaryService && _mapillary) { + } else if (!serviceMapillary && _mapillary) { _mapillary = null; } @@ -87,7 +88,7 @@ export function MapillaryImages(projection, context, dispatch) { function transform(d) { - var t = PointTransform(projection)(d); + var t = svgPointTransform(projection)(d); if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)'; return t; } @@ -95,7 +96,7 @@ export function MapillaryImages(projection, context, dispatch) { function update() { var mapillary = getMapillary(), - data = (mapillary ? mapillary.images(projection, getDimensions(layer)) : []), + data = (mapillary ? mapillary.images(projection, utilGetDimensions(layer)) : []), imageKey = mapillary ? mapillary.getSelectedImage() : null; var markers = layer.selectAll('.viewfield-group') @@ -127,7 +128,7 @@ export function MapillaryImages(projection, context, dispatch) { function drawImages(selection) { - var enabled = MapillaryImages.enabled, + var enabled = svgMapillaryImages.enabled, mapillary = getMapillary(); layer = selection.selectAll('.layer-mapillary-images') @@ -146,7 +147,7 @@ export function MapillaryImages(projection, context, dispatch) { if (mapillary && ~~context.map().zoom() >= minZoom) { editOn(); update(); - mapillary.loadImages(projection, getDimensions(layer)); + mapillary.loadImages(projection, utilGetDimensions(layer)); } else { editOff(); } @@ -155,9 +156,9 @@ export function MapillaryImages(projection, context, dispatch) { drawImages.enabled = function(_) { - if (!arguments.length) return MapillaryImages.enabled; - MapillaryImages.enabled = _; - if (MapillaryImages.enabled) { + if (!arguments.length) return svgMapillaryImages.enabled; + svgMapillaryImages.enabled = _; + if (svgMapillaryImages.enabled) { showLayer(); } else { hideLayer(); @@ -173,8 +174,8 @@ export function MapillaryImages(projection, context, dispatch) { drawImages.dimensions = function(_) { - if (!arguments.length) return getDimensions(layer); - setDimensions(layer, _); + if (!arguments.length) return utilGetDimensions(layer); + utilSetDimensions(layer, _); return this; }; diff --git a/modules/svg/mapillary_signs.js b/modules/svg/mapillary_signs.js index a0e9e4902..9aa8b6f52 100644 --- a/modules/svg/mapillary_signs.js +++ b/modules/svg/mapillary_signs.js @@ -1,11 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { getDimensions, setDimensions } from '../util/dimensions'; -import { PointTransform } from './point_transform'; -import { mapillary as mapillaryService } from '../services/index'; +import { utilGetDimensions, utilSetDimensions } from '../util/dimensions'; +import { svgPointTransform } from './point_transform'; +import { serviceMapillary } from '../services/index'; -export function MapillarySigns(projection, context, dispatch) { +export function svgMapillarySigns(projection, context, dispatch) { var debouncedRedraw = _.debounce(function () { dispatch.call('change'); }, 1000), minZoom = 12, layer = d3.select(null), @@ -13,17 +13,17 @@ export function MapillarySigns(projection, context, dispatch) { function init() { - if (MapillarySigns.initialized) return; // run once - MapillarySigns.enabled = false; - MapillarySigns.initialized = true; + if (svgMapillarySigns.initialized) return; // run once + svgMapillarySigns.enabled = false; + svgMapillarySigns.initialized = true; } function getMapillary() { - if (mapillaryService && !_mapillary) { - _mapillary = mapillaryService.init(); + if (serviceMapillary && !_mapillary) { + _mapillary = serviceMapillary.init(); _mapillary.event.on('loadedSigns', debouncedRedraw); - } else if (!mapillaryService && _mapillary) { + } else if (!serviceMapillary && _mapillary) { _mapillary = null; } return _mapillary; @@ -68,7 +68,7 @@ export function MapillarySigns(projection, context, dispatch) { function update() { var mapillary = getMapillary(), - data = (mapillary ? mapillary.signs(projection, getDimensions(layer)) : []), + data = (mapillary ? mapillary.signs(projection, utilGetDimensions(layer)) : []), imageKey = mapillary ? mapillary.getSelectedImage() : null; var signs = layer.selectAll('.icon-sign') @@ -91,12 +91,12 @@ export function MapillarySigns(projection, context, dispatch) { signs .merge(enter) - .attr('transform', PointTransform(projection)); + .attr('transform', svgPointTransform(projection)); } function drawSigns(selection) { - var enabled = MapillarySigns.enabled, + var enabled = svgMapillarySigns.enabled, mapillary = getMapillary(); layer = selection.selectAll('.layer-mapillary-signs') @@ -116,7 +116,7 @@ export function MapillarySigns(projection, context, dispatch) { if (mapillary && ~~context.map().zoom() >= minZoom) { editOn(); update(); - mapillary.loadSigns(context, projection, getDimensions(layer)); + mapillary.loadSigns(context, projection, utilGetDimensions(layer)); } else { editOff(); } @@ -125,9 +125,9 @@ export function MapillarySigns(projection, context, dispatch) { drawSigns.enabled = function(_) { - if (!arguments.length) return MapillarySigns.enabled; - MapillarySigns.enabled = _; - if (MapillarySigns.enabled) { + if (!arguments.length) return svgMapillarySigns.enabled; + svgMapillarySigns.enabled = _; + if (svgMapillarySigns.enabled) { showLayer(); } else { hideLayer(); @@ -144,8 +144,8 @@ export function MapillarySigns(projection, context, dispatch) { drawSigns.dimensions = function(_) { - if (!arguments.length) return getDimensions(layer); - setDimensions(layer, _); + if (!arguments.length) return utilGetDimensions(layer); + utilSetDimensions(layer, _); return this; }; diff --git a/modules/svg/midpoints.js b/modules/svg/midpoints.js index f134d15c7..2c88078e9 100644 --- a/modules/svg/midpoints.js +++ b/modules/svg/midpoints.js @@ -1,8 +1,19 @@ import _ from 'lodash'; -import { PointTransform, TagClasses } from './index'; -import { angle, euclideanDistance, interp, lineIntersection } from '../geo/index'; +import { + svgPointTransform, + svgTagClasses +} from './index'; + +import { + geoAngle, + geoEuclideanDistance, + geoInterp, + geoLineIntersection +} from '../geo/index'; + + +export function svgMidpoints(projection, context) { -export function Midpoints(projection, context) { return function drawMidpoints(selection, graph, entities, filter, extent) { var poly = extent.polygon(), midpoints = {}; @@ -27,18 +38,18 @@ export function Midpoints(projection, context) { if (midpoints[id]) { midpoints[id].parents.push(entity); } else { - if (euclideanDistance(projection(a.loc), projection(b.loc)) > 40) { - var point = interp(a.loc, b.loc, 0.5), + if (geoEuclideanDistance(projection(a.loc), projection(b.loc)) > 40) { + var point = geoInterp(a.loc, b.loc, 0.5), loc = null; if (extent.intersects(point)) { loc = point; } else { for (var k = 0; k < 4; k++) { - point = lineIntersection([a.loc, b.loc], [poly[k], poly[k+1]]); + point = geoLineIntersection([a.loc, b.loc], [poly[k], poly[k + 1]]); if (point && - euclideanDistance(projection(a.loc), projection(point)) > 20 && - euclideanDistance(projection(b.loc), projection(point)) > 20) + geoEuclideanDistance(projection(a.loc), projection(point)) > 20 && + geoEuclideanDistance(projection(b.loc), projection(point)) > 20) { loc = point; break; @@ -60,6 +71,7 @@ export function Midpoints(projection, context) { } } + function midpointFilter(d) { if (midpoints[d.id]) return true; @@ -102,10 +114,10 @@ export function Midpoints(projection, context) { var translate = PointTransform(projection), a = graph.entity(d.edge[0]), b = graph.entity(d.edge[1]), - angleVal = Math.round(angle(a, b, projection) * (180 / Math.PI)); + angleVal = Math.round(geoAngle(a, b, projection) * (180 / Math.PI)); return translate(d) + ' rotate(' + angleVal + ')'; }) - .call(TagClasses().tags( + .call(svgTagClasses().tags( function(d) { return d.parents[0].tags; } )); diff --git a/modules/svg/one_way_segments.js b/modules/svg/one_way_segments.js index 079a17972..e1568a620 100644 --- a/modules/svg/one_way_segments.js +++ b/modules/svg/one_way_segments.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; -import { euclideanDistance } from '../geo/index'; +import { geoEuclideanDistance } from '../geo/index'; -export function OneWaySegments(projection, graph, dt) { + +export function svgOneWaySegments(projection, graph, dt) { return function(entity) { var a, b, @@ -27,7 +28,7 @@ export function OneWaySegments(projection, graph, dt) { b = [x, y]; if (a) { - var span = euclideanDistance(a, b) - offset; + var span = geoEuclideanDistance(a, b) - offset; if (span >= 0) { var angle = Math.atan2(b[1] - a[1], b[0] - a[0]), diff --git a/modules/svg/osm.js b/modules/svg/osm.js index 8f5706cc0..4576ad9b5 100644 --- a/modules/svg/osm.js +++ b/modules/svg/osm.js @@ -1,4 +1,4 @@ -export function Osm() { +export function svgOsm() { return function drawOsm(selection) { var layers = selection.selectAll('.layer-osm') .data(['areas', 'lines', 'hit', 'halo', 'label']); diff --git a/modules/svg/path.js b/modules/svg/path.js index 0819b3bc8..e6091ed9e 100644 --- a/modules/svg/path.js +++ b/modules/svg/path.js @@ -1,6 +1,6 @@ import * as d3 from 'd3'; -export function Path(projection, graph, polygon) { +export function svgPath(projection, graph, polygon) { var cache = {}, clip = d3.geoClipExtent().extent(projection.clipExtent()).stream, project = projection.stream, diff --git a/modules/svg/point_transform.js b/modules/svg/point_transform.js index 313d82009..cf80f845d 100644 --- a/modules/svg/point_transform.js +++ b/modules/svg/point_transform.js @@ -1,4 +1,4 @@ -export function PointTransform(projection) { +export function svgPointTransform(projection) { return function(entity) { // http://jsperf.com/short-array-join var pt = projection(entity.loc); diff --git a/modules/svg/points.js b/modules/svg/points.js index 812acfc3f..150f8d609 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -1,9 +1,10 @@ import _ from 'lodash'; -import { PointTransform, TagClasses } from './index'; -import { Entity } from '../core/index'; +import { svgPointTransform, svgTagClasses } from './index'; +import { coreEntity } from '../core/index'; -export function Points(projection, context) { +export function svgPoints(projection, context) { + function markerPath(selection, klass) { selection .attr('class', klass) @@ -15,6 +16,7 @@ export function Points(projection, context) { return b.loc[1] - a.loc[1]; } + return function drawPoints(selection, graph, entities, filter) { var wireframe = context.surface().classed('fill-wireframe'), points = wireframe ? [] : _.filter(entities, function(e) { @@ -27,7 +29,7 @@ export function Points(projection, context) { var groups = layer.selectAll('g.point') .filter(filter) - .data(points, Entity.key); + .data(points, coreEntity.key); groups.exit() .remove(); @@ -51,8 +53,8 @@ export function Points(projection, context) { groups = groups .merge(enter) - .attr('transform', PointTransform(projection)) - .call(TagClasses()); + .attr('transform', svgPointTransform(projection)) + .call(svgTagClasses()); // Selecting the following implicitly // sets the data (point entity) on the element diff --git a/modules/svg/relation_member_tags.js b/modules/svg/relation_member_tags.js index 94fa6c567..7a4661eaf 100644 --- a/modules/svg/relation_member_tags.js +++ b/modules/svg/relation_member_tags.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -export function RelationMemberTags(graph) { +export function svgRelationMemberTags(graph) { return function(entity) { var tags = entity.tags; graph.parentRelations(entity).forEach(function(relation) { diff --git a/modules/svg/tag_classes.js b/modules/svg/tag_classes.js index c311f1b0b..ab501a5e7 100644 --- a/modules/svg/tag_classes.js +++ b/modules/svg/tag_classes.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; -import { pavedTags } from '../core/tags'; +import { corePavedTags } from '../core/tags'; -export function TagClasses() { + +export function svgTagClasses() { var primaries = [ 'building', 'highway', 'railway', 'waterway', 'aeroway', 'motorway', 'boundary', 'power', 'amenity', 'natural', 'landuse', @@ -88,8 +89,8 @@ export function TagClasses() { var paved = (t.highway !== 'track'); for (k in t) { v = t[k]; - if (k in pavedTags) { - paved = !!pavedTags[k][v]; + if (k in corePavedTags) { + paved = !!corePavedTags[k][v]; break; } } @@ -106,6 +107,7 @@ export function TagClasses() { }); }; + tagClasses.tags = function(_) { if (!arguments.length) return tags; tags = _; diff --git a/modules/svg/turns.js b/modules/svg/turns.js index 48d22d2d5..e2cd4d39b 100644 --- a/modules/svg/turns.js +++ b/modules/svg/turns.js @@ -1,8 +1,10 @@ -import { angle } from '../geo/index'; +import { geoAngle } from '../geo/index'; -export function Turns(projection) { +export function svgTurns(projection) { + return function drawTurns(selection, graph, turns) { + function key(turn) { return [turn.from.node + turn.via.node + turn.to.node].join('-'); } @@ -58,7 +60,7 @@ export function Turns(projection) { .attr('transform', function (turn) { var v = graph.entity(turn.via.node), t = graph.entity(turn.to.node), - a = angle(v, t, projection), + a = geoAngle(v, t, projection), p = projection(v.loc), r = turn.u ? 0 : 60; diff --git a/modules/svg/vertices.js b/modules/svg/vertices.js index 2e1649fa9..9038446fd 100644 --- a/modules/svg/vertices.js +++ b/modules/svg/vertices.js @@ -1,8 +1,9 @@ import * as d3 from 'd3'; -import { Entity } from '../core/index'; -import { PointTransform } from './index'; +import { coreEntity } from '../core/index'; +import { svgPointTransform } from './index'; -export function Vertices(projection, context) { + +export function svgVertices(projection, context) { var radiuses = { // z16-, z17, z18+, tagged shadow: [6, 7.5, 7.5, 11.5], @@ -101,7 +102,7 @@ export function Vertices(projection, context) { z = (zoom < 17 ? 0 : zoom < 18 ? 1 : 2); var groups = selection - .data(vertices, Entity.key); + .data(vertices, coreEntity.key); groups.exit() .remove(); @@ -132,7 +133,7 @@ export function Vertices(projection, context) { groups .merge(enter) - .attr('transform', PointTransform(projection)) + .attr('transform', svgPointTransform(projection)) .classed('shared', function(entity) { return graph.isShared(entity); }) .call(setAttributes); } diff --git a/modules/ui/account.js b/modules/ui/account.js index f92e559d4..3f1b41f58 100644 --- a/modules/ui/account.js +++ b/modules/ui/account.js @@ -1,10 +1,12 @@ import * as d3 from 'd3'; import { t } from '../util/locale'; -import { Icon } from '../svg/index'; +import { svgIcon } from '../svg/index'; -export function Account(context) { + +export function uiAccount(context) { var connection = context.connection(); + function update(selection) { if (!connection.authenticated()) { selection.selectAll('#userLink, #logoutLink') @@ -36,7 +38,7 @@ export function Account(context) { .attr('src', details.image_url); } else { userLink - .call(Icon('#icon-avatar', 'pre-text light')); + .call(svgIcon('#icon-avatar', 'pre-text light')); } // Add user name @@ -55,6 +57,7 @@ export function Account(context) { }); } + return function(selection) { selection.append('li') .attr('id', 'logoutLink') diff --git a/modules/ui/attribution.js b/modules/ui/attribution.js index 5d5bc5346..7f445e40f 100644 --- a/modules/ui/attribution.js +++ b/modules/ui/attribution.js @@ -2,9 +2,10 @@ import * as d3 from 'd3'; import _ from 'lodash'; -export function Attribution(context) { +export function uiAttribution(context) { var selection; + function attribution(data, klass) { var div = selection.selectAll('.' + klass) .data([0]); diff --git a/modules/ui/background.js b/modules/ui/background.js index 7826c1542..5334ea955 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -2,17 +2,18 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; +import { rendererBackgroundSource } from '../renderer/index'; +import { geoMetersToOffset, geoOffsetToMeters } from '../geo/index'; +import { utilDetect } from '../util/detect'; +import { utilSetTransform } from '../util/index'; +import { svgIcon } from '../svg/index'; +import { uiMapInMap } from './map_in_map'; +import { uiCmd } from './cmd'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { metersToOffset, offsetToMeters } from '../geo/index'; -import { BackgroundSource } from '../renderer/index'; -import { Detect } from '../util/detect'; -import { Icon } from '../svg/index'; -import { MapInMap } from './map_in_map'; -import { cmd } from './cmd'; -import { setTransform } from '../util/index'; -import { tooltipHtml } from './tooltipHtml'; -export function Background(context) { + +export function uiBackground(context) { var key = 'B', opacities = [1, 0.75, 0.5, 0.25], directions = [ @@ -37,14 +38,15 @@ export function Background(context) { : d3.descending(a.area(), b.area()) || d3.ascending(a.name(), b.name()) || 0; } + function setOpacity(d) { var bg = context.container().selectAll('.layer-background') .transition() .style('opacity', d) .attr('data-opacity', d); - if (!Detect().opera) { - setTransform(bg, 0, 0); + if (!utilDetect().opera) { + utilSetTransform(bg, 0, 0); } opacityList.selectAll('li') @@ -53,6 +55,7 @@ export function Background(context) { context.storage('background-opacity', d); } + function setTooltips(selection) { selection.each(function(d) { var item = d3.select(this); @@ -61,7 +64,7 @@ export function Background(context) { .html(true) .title(function() { var tip = '