From 8fe33d9430c3354953f4f2c7f4e6b7209ea06ed1 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 6 Apr 2017 17:09:32 -0400 Subject: [PATCH] Teach realigning roads by moving nodes, adding midpoints (closes #2381) --- data/core.yaml | 8 +- dist/locales/en.json | 8 +- modules/ui/intro/line.js | 261 ++++++++++++++++++++++++++++++--- modules/ui/intro/navigation.js | 4 +- 4 files changed, 256 insertions(+), 25 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index d5b4271f9..e81ebdb33 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -945,7 +945,13 @@ en: choose_preset_residential: "There are many different types of roads, but this one is a residential road. **Choose the {name} type**" retry_preset_residential: "You didn't select the {name} type. **Click here to choose again**" name_road: "**Give this road a name, then hit escape, return, or click the {button} button to close the feature editor.**" - play: "You added a new road! Try drawing a few more lines, and see what other kinds of lines you can add to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**" + update_line: "Sometimes you will need to change the shape of an existing line. Here is a road that doesn't look quite right." + add_node: "We can add some nodes to this line to improve its shape. One way to add a node is to double-click the line where you want to add a node. **Double-click on the line to create a new node.**" + start_drag_endpoint: "When a line is selected, you can drag any of its nodes by clicking and holding down the left mouse button while you drag. **Drag the endpoint to the place where these roads should intersect.**" + finish_drag_endpoint: "This spot looks good. **Finish dragging by releasing the left mouse button**" + start_drag_midpoint: "Small triangles are drawn at the midpoints between nodes. Another way to create a new node is to drag a midpoint to a new location. **Drag the midpoint triangle to create a new node along the curve of the road.**" + continue_drag_midpoint: "This line is looking much better! Continue to adjust this line by double-clicking or dragging midpoints until the curve matches the road shape. **When you're happy with how the line looks, click OK.**" + play: "Great! Use the skills that you've learned in this chapter to practice editing some more lines. **When you are ready to continue to the next chapter, click '{next}'.**" buildings: title: "Buildings" add_building: "OpenStreetMap is the world's largest database of buildings. You can help improve this database by tracing buildings that aren't already mapped. **Click the {button} Area button to add a new area.**" diff --git a/dist/locales/en.json b/dist/locales/en.json index 8bea99e5b..047faf7c6 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -808,7 +808,13 @@ "choose_preset_residential": "There are many different types of roads, but this one is a residential road. **Choose the {name} type**", "retry_preset_residential": "You didn't select the {name} type. **Click here to choose again**", "name_road": "**Give this road a name, then hit escape, return, or click the {button} button to close the feature editor.**", - "play": "You added a new road! Try drawing a few more lines, and see what other kinds of lines you can add to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**" + "update_line": "Sometimes you will need to change the shape of an existing line. Here is a road that doesn't look quite right.", + "add_node": "We can add some nodes to this line to improve its shape. One way to add a node is to double-click the line where you want to add a node. **Double-click on the line to create a new node.**", + "start_drag_endpoint": "When a line is selected, you can drag any of its nodes by clicking and holding down the left mouse button while you drag. **Drag the endpoint to the place where these roads should intersect.**", + "finish_drag_endpoint": "This spot looks good. **Finish dragging by releasing the left mouse button**", + "start_drag_midpoint": "Small triangles are drawn at the midpoints between nodes. Another way to create a new node is to drag a midpoint to a new location. **Drag the midpoint triangle to create a new node along the curve of the road.**", + "continue_drag_midpoint": "This line is looking much better! Continue to adjust this line by double-clicking or dragging midpoints until the curve matches the road shape. **When you're happy with how the line looks, click OK.**", + "play": "Great! Use the skills that you've learned in this chapter to practice editing some more lines. **When you are ready to continue to the next chapter, click '{next}'.**" }, "buildings": { "title": "Buildings", diff --git a/modules/ui/intro/line.js b/modules/ui/intro/line.js index bf856d70b..df73bd0c8 100644 --- a/modules/ui/intro/line.js +++ b/modules/ui/intro/line.js @@ -1,6 +1,8 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../../util/locale'; +import { geoSphericalDistance } from '../../geo/index'; +import { modeSelect } from '../../modes/select'; import { utilRebind } from '../../util/rebind'; import { icon, pad } from './helper'; @@ -8,13 +10,18 @@ import { icon, pad } from './helper'; export function uiIntroLine(context, reveal) { var dispatch = d3.dispatch('done'), timeouts = [], - midpoint = [-85.62975395449628, 41.95787501510204], - start = [-85.6297754121684, 41.95805253325314], - intersection = [-85.62974496187628, 41.95742515554585], + tulipRoadId = null, + flowerRoadId = 'w646', + tulipRoadStart = [-85.6297754121684, 41.95805253325314], + tulipRoadMidpoint = [-85.62975395449628, 41.95787501510204], + tulipRoadIntersection = [-85.62974496187628, 41.95742515554585], + woodRoadId = 'w525', + woodRoadEndId = 'n2862', + woodRoadAddNode = [-85.62390110349587, 41.95397111462291], + woodRoadDragEndpoint = [-85.62383958913921, 41.9546607846611], + woodRoadDragMidpoint = [-85.62386254803509, 41.95430395953872], roadCategory = context.presets().item('category-road'), - residentialPreset = context.presets().item('highway/residential'), - targetId = 'w646', - lineId = null; + residentialPreset = context.presets().item('highway/residential'); var chapter = { @@ -60,16 +67,16 @@ export function uiIntroLine(context, reveal) { return chapter.restart(); } - lineId = null; + tulipRoadId = null; var padding = 70 * Math.pow(2, context.map().zoom() - 18); - var box = pad(start, padding, context); + var box = pad(tulipRoadStart, padding, context); box.height = box.height + 100; reveal(box, t('intro.lines.start_line')); context.map().on('move.intro drawn.intro', function() { padding = 70 * Math.pow(2, context.map().zoom() - 18); - box = pad(start, padding, context); + box = pad(tulipRoadStart, padding, context); box.height = box.height + 100; reveal(box, t('intro.lines.start_line'), { duration: 0 }); }); @@ -92,12 +99,12 @@ export function uiIntroLine(context, reveal) { return chapter.restart(); } - lineId = context.mode().selectedIDs()[0]; - context.map().centerEase(midpoint); + tulipRoadId = context.mode().selectedIDs()[0]; + context.map().centerEase(tulipRoadMidpoint); timeout(function() { var padding = 200 * Math.pow(2, context.map().zoom() - 18.5); - var box = pad(midpoint, padding, context); + var box = pad(tulipRoadMidpoint, padding, context); box.height = box.height * 2; reveal(box, t('intro.lines.intersect', { name: t('intro.graph.name.flower-street') }) @@ -105,7 +112,7 @@ export function uiIntroLine(context, reveal) { context.map().on('move.intro drawn.intro', function() { padding = 200 * Math.pow(2, context.map().zoom() - 18.5); - box = pad(midpoint, padding, context); + box = pad(tulipRoadMidpoint, padding, context); box.height = box.height * 2; reveal(box, t('intro.lines.intersect', { name: t('intro.graph.name.flower-street') }), @@ -115,7 +122,7 @@ export function uiIntroLine(context, reveal) { }, 260); // after easing.. context.history().on('change.intro', function() { - var entity = lineId && context.hasEntity(lineId); + var entity = tulipRoadId && context.hasEntity(tulipRoadId); if (!entity) return chapter.restart(); if (isLineConnected()) { @@ -144,13 +151,13 @@ export function uiIntroLine(context, reveal) { function isLineConnected() { - var entity = lineId && context.hasEntity(lineId); + var entity = tulipRoadId && context.hasEntity(tulipRoadId); if (!entity) return false; var drawNodes = context.graph().childNodes(entity); return _.some(drawNodes, function(node) { return _.some(context.graph().parentWays(node), function(parent) { - return parent.id === targetId; + return parent.id === flowerRoadId; }); }); } @@ -159,7 +166,7 @@ export function uiIntroLine(context, reveal) { function retryIntersect() { d3.select(window).on('mousedown.intro', eventCancel, true); - var box = pad(intersection, 80, context); + var box = pad(tulipRoadIntersection, 80, context); reveal(box, t('intro.lines.retry_intersect', { name: t('intro.graph.name.flower-street') }) ); @@ -170,10 +177,10 @@ export function uiIntroLine(context, reveal) { function continueLine() { if (context.mode().id !== 'draw-line') return chapter.restart(); - var entity = lineId && context.hasEntity(lineId); + var entity = tulipRoadId && context.hasEntity(tulipRoadId); if (!entity) return chapter.restart(); - context.map().centerEase(intersection); + context.map().centerEase(tulipRoadIntersection); reveal('#surface', t('intro.lines.continue_line')); @@ -287,7 +294,8 @@ export function uiIntroLine(context, reveal) { function nameRoad() { context.on('exit.intro', function() { - continueTo(play); + context.history().checkpoint('doneAddRoad'); + continueTo(updateLine); }); timeout(function() { @@ -303,6 +311,217 @@ export function uiIntroLine(context, reveal) { } + function updateLine() { + context.history().reset('doneAddRoad'); + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return chapter.restart(); + } + + context.map().zoom(19).centerEase(woodRoadDragMidpoint, 500); + + timeout(function() { + var padding = 250 * Math.pow(2, context.map().zoom() - 19); + var box = pad(woodRoadDragMidpoint, padding, context); + var advance = function() { continueTo(addNode); }; + + reveal(box, t('intro.lines.update_line'), + { buttonText: t('intro.ok'), buttonCallback: advance } + ); + + context.map().on('move.intro drawn.intro', function() { + var box = pad(woodRoadDragMidpoint, padding, context); + reveal(box, t('intro.lines.update_line'), + { duration: 0, buttonText: t('intro.ok'), buttonCallback: advance } + ); + }); + }, 550); + + function continueTo(nextStep) { + context.map().on('move.intro drawn.intro', null); + nextStep(); + } + } + + + function addNode() { + context.history().reset('doneAddRoad'); + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return chapter.restart(); + } + + var padding = 40 * Math.pow(2, context.map().zoom() - 19); + + var box = pad(woodRoadAddNode, padding, context); + reveal(box, t('intro.lines.add_node')); + + context.map().on('move.intro drawn.intro', function() { + var box = pad(woodRoadAddNode, padding, context); + reveal(box, t('intro.lines.add_node'), { duration: 0 }); + }); + + context.history().on('change.intro', function(changed) { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + if (changed.created().length === 1) { + timeout(function() { continueTo(startDragEndpoint); }, 500); + } + }); + + context.on('enter.intro', function(mode) { + if (mode.id !== 'select') { + continueTo(updateLine); + } + }); + + function continueTo(nextStep) { + context.map().on('move.intro drawn.intro', null); + context.history().on('change.intro', null); + context.on('enter.intro', null); + nextStep(); + } + } + + + function startDragEndpoint() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + + var padding = 100 * Math.pow(2, context.map().zoom() - 19); + + var box = pad(woodRoadDragEndpoint, padding, context); + reveal(box, t('intro.lines.start_drag_endpoint')); + + context.map().on('move.intro drawn.intro', function() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + + var box = pad(woodRoadDragEndpoint, padding, context); + reveal(box, t('intro.lines.start_drag_endpoint'), { duration: 0 }); + + var entity = context.entity(woodRoadEndId); + if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) <= 2) { + continueTo(finishDragEndpoint); + } + }); + + function continueTo(nextStep) { + context.map().on('move.intro drawn.intro', null); + nextStep(); + } + } + + + function finishDragEndpoint() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + + var padding = 100 * Math.pow(2, context.map().zoom() - 19); + + var box = pad(woodRoadDragEndpoint, padding, context); + reveal(box, t('intro.lines.finish_drag_endpoint')); + + context.map().on('move.intro drawn.intro', function() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + var box = pad(woodRoadDragEndpoint, padding, context); + reveal(box, t('intro.lines.finish_drag_endpoint'), { duration: 0 }); + + var entity = context.entity(woodRoadEndId); + if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) > 2.5) { + continueTo(startDragEndpoint); + } + }); + + context.on('enter.intro', function() { + continueTo(startDragMidpoint); + }); + + function continueTo(nextStep) { + context.map().on('move.intro drawn.intro', null); + context.on('enter.intro', null); + nextStep(); + } + } + + + function startDragMidpoint() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + + var padding = 80 * Math.pow(2, context.map().zoom() - 19); + + var box = pad(woodRoadDragMidpoint, padding, context); + reveal(box, t('intro.lines.start_drag_midpoint')); + + context.map().on('move.intro drawn.intro', function() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + var box = pad(woodRoadDragMidpoint, padding, context); + reveal(box, t('intro.lines.start_drag_midpoint'), { duration: 0 }); + }); + + context.history().on('change.intro', function(changed) { + if (changed.created().length === 1) { + continueTo(continueDragMidpoint); + } + }); + + context.on('enter.intro', function(mode) { + if (mode.id !== 'select') { + // keep Wood Road selected so midpoint triangles are drawn.. + context.enter(modeSelect(context, [woodRoadId])); + } + }); + + function continueTo(nextStep) { + context.map().on('move.intro drawn.intro', null); + context.history().on('change.intro', null); + context.on('enter.intro', null); + nextStep(); + } + } + + + function continueDragMidpoint() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + + var padding = 100 * Math.pow(2, context.map().zoom() - 19); + + var box = pad(woodRoadDragEndpoint, padding, context); + box.height += 400; + var advance = function() { continueTo(play); }; + + reveal(box, t('intro.lines.continue_drag_midpoint'), + { buttonText: t('intro.ok'), buttonCallback: advance } + ); + + context.map().on('move.intro drawn.intro', function() { + if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) { + return continueTo(updateLine); + } + var box = pad(woodRoadDragEndpoint, padding, context); + box.height += 400; + reveal(box, t('intro.lines.continue_drag_midpoint'), + { duration: 0, buttonText: t('intro.ok'), buttonCallback: advance } + ); + }); + + function continueTo(nextStep) { + context.map().on('move.intro drawn.intro', null); + nextStep(); + } + } + + function play() { dispatch.call('done'); reveal('.intro-nav-wrap .chapter-building', @@ -316,7 +535,7 @@ export function uiIntroLine(context, reveal) { chapter.enter = function() { context.history().reset('initial'); - context.map().zoom(18.5).centerEase(start); + context.map().zoom(18.5).centerEase(tulipRoadStart); addLine(); }; diff --git a/modules/ui/intro/navigation.js b/modules/ui/intro/navigation.js index 9f5133f91..16f4f5387 100644 --- a/modules/ui/intro/navigation.js +++ b/modules/ui/intro/navigation.js @@ -6,8 +6,9 @@ import { icon, pointBox } from './helper'; export function uiIntroNavigation(context, reveal) { var dispatch = d3.dispatch('done'), + timeouts = [], hallId = 'n2061', - timeouts = []; + springSt = [-85.63585099140167, 41.942506848938926]; var chapter = { @@ -174,7 +175,6 @@ export function uiIntroNavigation(context, reveal) { function selectedStreet() { - var springSt = [-85.63585099140167, 41.942506848938926]; context.map().centerEase(springSt); timeout(function() {