Index: data/defaultpresets.xml
===================================================================
--- data/defaultpresets.xml	(revision 15556)
+++ data/defaultpresets.xml	(working copy)
@@ -7580,6 +7580,19 @@
                 <role key="to" text="to way" requisite="required" count="1" type="way" />
             </roles>
         </item> <!-- Turn Restriction -->
+        <item name="Lane Connectivity" type="relation" preset_name_label="true" icon="presets/transport/way/relation_connectivity.svg">
+            <link wiki="Relation:connectivity" />
+            <space />
+            <key key="type" value="connectivity" />
+            <optional>
+                <text key="connectivity" text="Lane Connectivity" />
+            </optional>
+            <roles>
+                <role key="from" text="from way" requisite="required" count="1" type="way" />
+                <role key="via" text="via node or ways" requisite="required" type="way,node" />
+                <role key="to" text="to way" requisite="required" count="1" type="way" />
+            </roles>
+        </item> <!-- Lane Connectivity -->
         <item name="Enforcement" icon="presets/vehicle/restriction/speed_camera.svg" type="relation" preset_name_label="true">
             <link wiki="Relation:enforcement" />
             <space />
Index: images/presets/transport/way/relation_connectivity.svg
===================================================================
--- images/presets/transport/way/relation_connectivity.svg	(nonexistent)
+++ images/presets/transport/way/relation_connectivity.svg	(working copy)
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 640 640" width="640" height="640"><defs><path d="M0 46.49C0 46.49 0 46.49 0 46.49C0 20.82 20.82 0 46.49 0C59.5 0 124.53 0 241.58 0C241.58 0 241.58 0 241.58 0C253.92 0 265.74 4.9 274.46 13.62C283.18 22.34 288.08 34.16 288.08 46.49C288.08 65.09 288.08 213.86 288.08 232.46C288.08 258.14 267.26 278.95 241.58 278.95C222.08 278.95 66 278.95 46.49 278.95C20.82 278.95 0 258.14 0 232.46C0 207.66 0 145.67 0 46.49Z" id="a7taVMJVF2"></path><path d="M0 46.49C0 46.49 0 46.49 0 46.49C0 20.82 20.82 0 46.49 0C59.5 0 124.53 0 241.58 0C241.58 0 241.58 0 241.58 0C253.92 0 265.74 4.9 274.46 13.62C283.18 22.34 288.08 34.16 288.08 46.49C288.08 65.09 288.08 213.86 288.08 232.46C288.08 258.14 267.26 278.95 241.58 278.95C222.08 278.95 66 278.95 46.49 278.95C20.82 278.95 0 258.14 0 232.46C0 207.66 0 145.67 0 46.49Z" id="apgHjUq2Q"></path><path d="M351.92 46.49C351.92 46.49 351.92 46.49 351.92 46.49C351.92 20.82 372.74 0 398.42 0C411.42 0 476.45 0 593.51 0C593.51 0 593.51 0 593.51 0C605.84 0 617.66 4.9 626.38 13.62C635.1 22.34 640 34.16 640 46.49C640 65.09 640 213.86 640 232.46C640 258.14 619.18 278.95 593.51 278.95C574 278.95 417.92 278.95 398.42 278.95C372.74 278.95 351.92 258.14 351.92 232.46C351.92 207.66 351.92 145.67 351.92 46.49Z" id="c2kLrQggK"></path><path d="M351.92 46.49C351.92 46.49 351.92 46.49 351.92 46.49C351.92 20.82 372.74 0 398.42 0C411.42 0 476.45 0 593.51 0C593.51 0 593.51 0 593.51 0C605.84 0 617.66 4.9 626.38 13.62C635.1 22.34 640 34.16 640 46.49C640 65.09 640 213.86 640 232.46C640 258.14 619.18 278.95 593.51 278.95C574 278.95 417.92 278.95 398.42 278.95C372.74 278.95 351.92 258.14 351.92 232.46C351.92 207.66 351.92 145.67 351.92 46.49Z" id="bRf59UWe"></path><path d="M351.92 407.54C351.92 407.54 351.92 407.54 351.92 407.54C351.92 381.86 372.74 361.05 398.42 361.05C411.42 361.05 476.45 361.05 593.51 361.05C593.51 361.05 593.51 361.05 593.51 361.05C605.84 361.05 617.66 365.95 626.38 374.67C635.1 383.39 640 395.21 640 407.54C640 426.14 640 574.91 640 593.51C640 619.18 619.18 640 593.51 640C574 640 417.92 640 398.42 640C372.74 640 351.92 619.18 351.92 593.51C351.92 568.71 351.92 506.72 351.92 407.54Z" id="b3XpfhE8d"></path><path d="M351.92 407.54C351.92 407.54 351.92 407.54 351.92 407.54C351.92 381.86 372.74 361.05 398.42 361.05C411.42 361.05 476.45 361.05 593.51 361.05C593.51 361.05 593.51 361.05 593.51 361.05C605.84 361.05 617.66 365.95 626.38 374.67C635.1 383.39 640 395.21 640 407.54C640 426.14 640 574.91 640 593.51C640 619.18 619.18 640 593.51 640C574 640 417.92 640 398.42 640C372.74 640 351.92 619.18 351.92 593.51C351.92 568.71 351.92 506.72 351.92 407.54Z" id="g6peWbU7wg"></path><path d="M0 407.54C0 407.54 0 407.54 0 407.54C0 381.86 20.82 361.05 46.49 361.05C59.5 361.05 124.53 361.05 241.58 361.05C241.58 361.05 241.58 361.05 241.58 361.05C253.92 361.05 265.74 365.95 274.46 374.67C283.18 383.39 288.08 395.21 288.08 407.54C288.08 426.14 288.08 574.91 288.08 593.51C288.08 619.18 267.26 640 241.58 640C222.08 640 66 640 46.49 640C20.82 640 0 619.18 0 593.51C0 568.71 0 506.72 0 407.54Z" id="b3X9mZS0XS"></path><path d="M0 407.54C0 407.54 0 407.54 0 407.54C0 381.86 20.82 361.05 46.49 361.05C59.5 361.05 124.53 361.05 241.58 361.05C241.58 361.05 241.58 361.05 241.58 361.05C253.92 361.05 265.74 365.95 274.46 374.67C283.18 383.39 288.08 395.21 288.08 407.54C288.08 426.14 288.08 574.91 288.08 593.51C288.08 619.18 267.26 640 241.58 640C222.08 640 66 640 46.49 640C20.82 640 0 619.18 0 593.51C0 568.71 0 506.72 0 407.54Z" id="c2OicV7Ga1"></path><path d="M0 230.92C0 230.92 0 230.92 0 230.92C0 202.89 22.73 180.16 50.77 180.16C104.61 180.16 535.39 180.16 589.23 180.16C602.7 180.16 615.61 185.51 625.13 195.03C634.65 204.55 640 217.46 640 230.92C640 251.23 640 413.67 640 433.98C640 462.01 617.27 484.74 589.23 484.74C535.39 484.74 104.61 484.74 50.77 484.74C22.73 484.74 0 462.01 0 433.98C0 406.9 0 339.22 0 230.92Z" id="aaiiO6LMf9"></path><path d="M165.66 48.01C165.66 48.01 165.66 48.01 165.66 48.01C165.66 21.5 187.16 0 213.68 0C226.48 0 290.5 0 405.73 0C405.73 0 405.73 0 405.73 0C418.46 0 430.67 5.06 439.68 14.06C448.68 23.07 453.74 35.28 453.74 48.01C453.74 102.41 453.74 537.59 453.74 591.99C453.74 618.5 432.25 640 405.73 640C386.52 640 232.88 640 213.68 640C187.16 640 165.66 618.5 165.66 591.99C165.66 519.46 165.66 338.13 165.66 48.01Z" id="abbTqBwz"></path><path d="M202.77 0L428.27 0" id="h8eVNYgClu"></path><path d="M202.77 0L428.27 0" id="b3wCehSi4F"></path><path d="M196.95 640L422.45 640" id="a8ZF3dFoi"></path><path d="M196.95 640L422.45 640" id="g2XjesW754"></path><path d="M640 459.85L639.37 203.2" id="c1aq5Qb0th"></path><path d="M640 459.85L639.37 203.2" id="b5fB0Jggyg"></path><path d="M0.63 466.77L0 210.12" id="e1w7hZayOo"></path><path d="M0.63 466.77L0 210.12" id="dHcZYYnhO"></path><path d="M37.55 47.79C37.55 47.79 37.55 47.79 37.55 47.79C37.55 40.11 43.78 33.88 51.47 33.88C55.18 33.88 73.73 33.88 107.12 33.88C107.12 33.88 107.12 33.88 107.12 33.88C110.81 33.88 114.35 35.34 116.96 37.95C119.57 40.56 121.04 44.1 121.04 47.79C121.04 55.94 121.04 121.1 121.04 129.24C121.04 136.93 114.81 143.16 107.12 143.16C101.56 143.16 57.03 143.16 51.47 143.16C43.78 143.16 37.55 136.93 37.55 129.24C37.55 118.38 37.55 91.23 37.55 47.79Z" id="bGa13pfAK"></path><path d="M519.5 47.79C519.5 47.79 519.5 47.79 519.5 47.79C519.5 40.11 525.73 33.88 533.42 33.88C537.13 33.88 555.68 33.88 589.07 33.88C589.07 33.88 589.07 33.88 589.07 33.88C592.76 33.88 596.3 35.34 598.91 37.95C601.52 40.56 602.99 44.1 602.99 47.79C602.99 55.94 602.99 121.1 602.99 129.24C602.99 136.93 596.76 143.16 589.07 143.16C583.51 143.16 538.98 143.16 533.42 143.16C525.73 143.16 519.5 136.93 519.5 129.24C519.5 118.38 519.5 91.23 519.5 47.79Z" id="d2wCkNoM3P"></path><path d="M37.55 507.57C37.55 507.57 37.55 507.57 37.55 507.57C37.55 499.89 43.78 493.66 51.47 493.66C55.18 493.66 73.73 493.66 107.12 493.66C107.12 493.66 107.12 493.66 107.12 493.66C110.81 493.66 114.35 495.13 116.96 497.74C119.57 500.34 121.04 503.88 121.04 507.57C121.04 515.72 121.04 580.88 121.04 589.02C121.04 596.71 114.81 602.94 107.12 602.94C101.56 602.94 57.03 602.94 51.47 602.94C43.78 602.94 37.55 596.71 37.55 589.02C37.55 578.16 37.55 551.01 37.55 507.57Z" id="g1Te9z9uK"></path><path d="M519.5 507.57C519.5 507.57 519.5 507.57 519.5 507.57C519.5 499.89 525.73 493.66 533.42 493.66C537.13 493.66 555.68 493.66 589.07 493.66C589.07 493.66 589.07 493.66 589.07 493.66C592.76 493.66 596.3 495.13 598.91 497.74C601.52 500.34 602.99 503.88 602.99 507.57C602.99 515.72 602.99 580.88 602.99 589.02C602.99 596.71 596.76 602.94 589.07 602.94C583.51 602.94 538.98 602.94 533.42 602.94C525.73 602.94 519.5 596.71 519.5 589.02C519.5 578.16 519.5 551.01 519.5 507.57Z" id="a5xlHsGyW"></path><path d="M94.04 33.88L545.32 33.88L545.32 602.93L94.04 602.93L94.04 33.88Z" id="i5XppWnd6A"></path><path d="M37.55 98.96L602.99 98.96L602.99 537.35L37.55 537.35L37.55 98.96Z" id="a1npIiv3d"></path><path d="M428.27 157.29L478.88 65.19L529.49 157.29L428.27 157.29Z" id="aGrCMZldo"></path><path d="M264.9 157.29L315.52 65.19L366.13 157.29L264.9 157.29Z" id="a2vqq6wfRW"></path><path d="M309.92 151.76L309.48 563.17" id="a4mZiuAN4"></path><path d="M309.92 151.76L309.48 563.17" id="f2eR1MdjtU"></path><path d="M320.22 151.76L319.78 563.17" id="i1aw7I6IO"></path><path d="M320.22 151.76L319.78 563.17" id="aiXcaY4gb"></path><path d="M474.86 151.76L474.42 563.17" id="g5YujQDwx"></path><path d="M474.86 151.76L474.42 563.17" id="c4xBbdTiNV"></path><path d="M485.16 151.76L484.72 563.17" id="ciAys2Kx0"></path><path d="M485.16 151.76L484.72 563.17" id="b2UTI2pbY"></path><path d="M146.84 453.33C146.84 395.62 187.67 366.76 228.5 337.91C269.34 309.05 310.17 280.19 310.17 222.48" id="a17DFS0xbs"></path><path d="M146.84 453.33C146.84 395.62 187.67 366.76 228.5 337.91C269.34 309.05 310.17 280.19 310.17 222.48" id="asAo4qo53"></path><path d="M155.87 453.33C155.87 395.62 196.7 366.76 237.53 337.91C278.36 309.05 319.19 280.19 319.19 222.48" id="c353BxmIjg"></path><path d="M155.87 453.33C155.87 395.62 196.7 366.76 237.53 337.91C278.36 309.05 319.19 280.19 319.19 222.48" id="bNwkHbP5N"></path><path d="M146.84 450.88L144.54 563.13" id="aRkZLonUJ"></path><path d="M146.84 450.88L144.54 563.13" id="gCYFFvttG"></path><path d="M155.44 434.91L154.84 563.16" id="e4poeBsfvT"></path><path d="M155.44 434.91L154.84 563.16" id="aTTBmkyhq"></path></defs><g><g><g><use xlink:href="#a7taVMJVF2" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#a7taVMJVF2" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#apgHjUq2Q" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#apgHjUq2Q" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#c2kLrQggK" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#c2kLrQggK" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#bRf59UWe" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#bRf59UWe" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#b3XpfhE8d" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#b3XpfhE8d" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#g6peWbU7wg" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#g6peWbU7wg" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#b3X9mZS0XS" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#b3X9mZS0XS" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#c2OicV7Ga1" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#c2OicV7Ga1" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#aaiiO6LMf9" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#aaiiO6LMf9" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#abbTqBwz" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#abbTqBwz" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#h8eVNYgClu" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#h8eVNYgClu" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b3wCehSi4F" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#b3wCehSi4F" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#a8ZF3dFoi" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#a8ZF3dFoi" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#g2XjesW754" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#g2XjesW754" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#c1aq5Qb0th" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#c1aq5Qb0th" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b5fB0Jggyg" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#b5fB0Jggyg" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#e1w7hZayOo" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#e1w7hZayOo" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#dHcZYYnhO" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#dHcZYYnhO" opacity="1" fill-opacity="0" stroke="#595959" stroke-width="1" stroke-opacity="1"></use></g></g><g><use xlink:href="#bGa13pfAK" opacity="1" fill="#0000ff" fill-opacity="1"></use><g><use xlink:href="#bGa13pfAK" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#d2wCkNoM3P" opacity="1" fill="#0000ff" fill-opacity="1"></use><g><use xlink:href="#d2wCkNoM3P" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#g1Te9z9uK" opacity="1" fill="#0000ff" fill-opacity="1"></use><g><use xlink:href="#g1Te9z9uK" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a5xlHsGyW" opacity="1" fill="#0000ff" fill-opacity="1"></use><g><use xlink:href="#a5xlHsGyW" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#i5XppWnd6A" opacity="1" fill="#0000ff" fill-opacity="1"></use><g><use xlink:href="#i5XppWnd6A" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a1npIiv3d" opacity="1" fill="#0000ff" fill-opacity="1"></use><g><use xlink:href="#a1npIiv3d" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#aGrCMZldo" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#aGrCMZldo" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a2vqq6wfRW" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#a2vqq6wfRW" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a4mZiuAN4" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#a4mZiuAN4" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#f2eR1MdjtU" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#f2eR1MdjtU" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g><g><use xlink:href="#i1aw7I6IO" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#i1aw7I6IO" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#aiXcaY4gb" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#aiXcaY4gb" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g><g><use xlink:href="#g5YujQDwx" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#g5YujQDwx" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#c4xBbdTiNV" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#c4xBbdTiNV" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g><g><use xlink:href="#ciAys2Kx0" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#ciAys2Kx0" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b2UTI2pbY" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#b2UTI2pbY" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g><g><use xlink:href="#a17DFS0xbs" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#a17DFS0xbs" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#asAo4qo53" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#asAo4qo53" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g><g><use xlink:href="#c353BxmIjg" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#c353BxmIjg" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#bNwkHbP5N" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#bNwkHbP5N" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g><g><use xlink:href="#aRkZLonUJ" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#aRkZLonUJ" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#gCYFFvttG" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#gCYFFvttG" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g><g><use xlink:href="#e4poeBsfvT" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#e4poeBsfvT" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#aTTBmkyhq" opacity="1" fill="#000000" fill-opacity="0"></use><g><use xlink:href="#aTTBmkyhq" opacity="1" fill-opacity="0" stroke="#ffffff" stroke-width="24" stroke-opacity="1"></use></g></g></g></g></svg>
\ No newline at end of file
Index: src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 15556)
+++ src/org/openstreetmap/josm/data/validation/OsmValidator.java	(working copy)
@@ -42,6 +42,7 @@
 import org.openstreetmap.josm.data.validation.tests.BarriersEntrances;
 import org.openstreetmap.josm.data.validation.tests.Coastlines;
 import org.openstreetmap.josm.data.validation.tests.ConditionalKeys;
+import org.openstreetmap.josm.data.validation.tests.ConnectivityRelations;
 import org.openstreetmap.josm.data.validation.tests.CrossingWays;
 import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
 import org.openstreetmap.josm.data.validation.tests.DuplicateRelation;
@@ -150,6 +151,7 @@
         PublicTransportRouteTest.class, // 3600 .. 3699
         RightAngleBuildingTest.class, // 3700 .. 3799
         SharpAngles.class, // 3800 .. 3899
+        ConnectivityRelations.class, // 3900 .. 3999
     };

     /**
Index: src/org/openstreetmap/josm/data/validation/tests/ConnectivityRelations.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/ConnectivityRelations.java	(nonexistent)
+++ src/org/openstreetmap/josm/data/validation/tests/ConnectivityRelations.java	(working copy)
@@ -0,0 +1,186 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.tools.Logging;
+
+/**
+ * Check for inconsistencies in lane information between relation and members.
+ */
+public class ConnectivityRelations extends Test {
+
+    protected static final int INCONSISTENT_LANE_COUNT = 3900;
+
+    protected static final int UNKNOWN_CONNECTIVITY_ROLE = INCONSISTENT_LANE_COUNT + 1;
+
+    protected static final int NO_CONNECTIVITY_TAG = INCONSISTENT_LANE_COUNT + 2;
+
+    protected static final int TOO_MANY_ROLES = INCONSISTENT_LANE_COUNT + 3;
+
+    private static final String CONNECTIVITY_TAG = "connectivity";
+    private static final String VIA = "via";
+    private static final String TO = "to";
+    private static final String FROM = "from";
+    private static final Pattern OPTIONAL_LANE_PATTERN = Pattern.compile("\\([0-9]+\\)");
+    private static final Pattern TO_LANE_PATTERN = Pattern.compile("\\p{Zs}*[,:;]\\p{Zs}*");
+
+    /**
+    * Constructor
+    */
+    public ConnectivityRelations() {
+        super(tr("Connectivity Relation Check"), tr("Checks that lane count of relation matches with lanes of members"));
+    }
+
+    /**
+     * Convert the connectivity tag into a map of values
+     *
+     * @param relation A relation with a {@code connectivity} tag.
+     * @return A Map in the form of {@code Map<Lane From, Map<Lane To, Optional>>} May contain nulls when errors are encountered
+     * @since xxx
+     */
+    public static Map<Integer, Map<Integer, Boolean>> parseConnectivityTag(Relation relation) {
+        final String joined = relation.get(CONNECTIVITY_TAG);
+
+        if (joined == null) {
+            return Collections.emptyMap();
+        }
+
+        final Map<Integer, Map<Integer, Boolean>> result = new HashMap<>();
+        String[] lanes = joined.split("\\|", -1);
+        for (int i = 0; i < lanes.length; i++) {
+            String[] lane = lanes[i].split(":", -1);
+            int laneNumber = Integer.parseInt(lane[0].trim());
+            Map<Integer, Boolean> connections = new HashMap<>();
+            String[] toLanes = TO_LANE_PATTERN.split(lane[1]);
+            for (int j = 0; j < toLanes.length; j++) {
+                String toLane = toLanes[j].trim();
+                try {
+                    if (OPTIONAL_LANE_PATTERN.matcher(toLane).matches()) {
+                        toLane = toLane.replace("(", "").replace(")", "").trim();
+                        connections.put(Integer.parseInt(toLane), Boolean.TRUE);
+                    } else {
+                        connections.put(Integer.parseInt(toLane), Boolean.FALSE);
+                    }
+                } catch (NumberFormatException e) {
+                    Logging.debug(e);
+                    connections.put(null, null);
+                }
+
+            }
+            result.put(laneNumber, connections);
+        }
+        return result;
+    }
+
+    @Override
+    public void visit(Relation r) {
+        if (r.hasTag("type", CONNECTIVITY_TAG)) {
+            if (!r.hasKey(CONNECTIVITY_TAG)) {
+                errors.add(TestError.builder(this, Severity.WARNING, NO_CONNECTIVITY_TAG)
+                        .message(tr("No connectivity tag in connectivity relation")).primitives(r).build());
+            } else if (!r.hasIncompleteMembers()) {
+                boolean badRole = checkForBadRole(r);
+                if (!badRole)
+                    checkForInconsistentLanes(r);
+            }
+        }
+    }
+
+    private void checkForInconsistentLanes(Relation relation) {
+        // Lane count from connectivity tag
+        Map<Integer, Map<Integer, Boolean>> connTagLanes = parseConnectivityTag(relation);
+        // Lane count from member tags
+        Map<String, Integer> roleLanes = new HashMap<>();
+
+        for (RelationMember rM : relation.getMembers()) {
+            // Check lanes
+            if (rM.getType() == OsmPrimitiveType.WAY) {
+                OsmPrimitive prim = rM.getMember();
+                if (prim.hasKey("lanes") && !VIA.equals(rM.getRole())) {
+                    roleLanes.put(rM.getRole(), Integer.parseInt(prim.get("lanes")));
+                }
+            }
+        }
+        boolean fromCheck = roleLanes.get(FROM) < Collections
+                .max(connTagLanes.entrySet(), Comparator.comparingInt(Map.Entry::getKey)).getKey();
+        boolean toCheck = false;
+        for (Entry<Integer, Map<Integer, Boolean>> to : connTagLanes.entrySet()) {
+            toCheck = roleLanes.get(TO) < Collections
+                    .max(to.getValue().entrySet(), Comparator.comparingInt(Map.Entry::getKey)).getKey();
+        }
+        if (fromCheck || toCheck) {
+            errors.add(TestError.builder(this, Severity.WARNING, INCONSISTENT_LANE_COUNT)
+                    .message(tr("Inconsistent lane numbering between relation and members")).primitives(relation)
+                    .build());
+        }
+    }
+
+    private boolean checkForBadRole(Relation relation) {
+        // Check role names
+        int viaWays = 0;
+        int viaNodes = 0;
+        int toWays = 0;
+        int fromWays = 0;
+        for (RelationMember relationMember : relation.getMembers()) {
+            if (relationMember.getMember() instanceof Way) {
+                if (relationMember.hasRole(FROM))
+                    fromWays++;
+                else if (relationMember.hasRole(TO))
+                    toWays++;
+                else if (relationMember.hasRole(VIA))
+                    viaWays++;
+                else {
+                    createUnknownRole(relation, relationMember.getMember());
+                    return true;
+                }
+            } else if (relationMember.getMember() instanceof Node) {
+                if (!relationMember.hasRole(VIA)) {
+                    createUnknownRole(relation, relationMember.getMember());
+                    return true;
+                }
+                viaNodes++;
+            }
+        }
+        return mixedViaNodeAndWay(relation, viaWays, viaNodes, toWays, fromWays);
+    }
+
+    private boolean mixedViaNodeAndWay(Relation relation, int viaWays, int viaNodes, int toWays, int fromWays) {
+        String message = "";
+        if ((viaWays != 0 && viaNodes != 0) || viaNodes > 1) {
+            message = tr("Relation contains {1} {0} roles.", VIA, viaWays + viaNodes);
+        } else if (toWays != 1) {
+            message = tr("Relation contains too many {0} roles", TO);
+        } else if (fromWays != 1) {
+            message = tr("Relation contains too many {0} roles", FROM);
+        }
+        if (message.isEmpty()) {
+            return false;
+        } else {
+            errors.add(TestError.builder(this, Severity.WARNING, TOO_MANY_ROLES)
+                    .message(message).primitives(relation).build());
+            return true;
+        }
+    }
+
+    private void createUnknownRole(Relation relation, OsmPrimitive primitive) {
+        errors.add(TestError.builder(this, Severity.WARNING, UNKNOWN_CONNECTIVITY_ROLE)
+                .message(tr("Unkown role in connectivity relation")).primitives(relation).highlight(primitive).build());
+    }
+}
Index: test/unit/org/openstreetmap/josm/data/validation/tests/ConnectivityRelationsTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/validation/tests/ConnectivityRelationsTest.java	(nonexistent)
+++ test/unit/org/openstreetmap/josm/data/validation/tests/ConnectivityRelationsTest.java	(working copy)
@@ -0,0 +1,116 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+
+/**
+ * Test the ConnectivityRelations validation test
+ *
+ * @author Taylor Smock
+ */
+public class ConnectivityRelationsTest {
+    private ConnectivityRelations check;
+    private static final String CONNECTIVITY = "connectivity";
+    /**
+     * Setup test.
+     *
+     * @throws Exception if an error occurs
+     */
+    @Before
+    public void setUp() throws Exception {
+        JOSMFixture.createUnitTestFixture().init();
+        check = new ConnectivityRelations();
+    }
+
+    private Relation createDefaultTestRelation() {
+        Node connection = new Node(new LatLon(0, 0));
+        return TestUtils.newRelation("type=connectivity connectivity=1:1",
+                new RelationMember("from", TestUtils.newWay("lanes=4", new Node(new LatLon(-0.1, -0.1)), connection)),
+                new RelationMember("via", connection),
+                new RelationMember("to", TestUtils.newWay("lanes=4", connection, new Node(new LatLon(0.1, 0.1)))));
+    }
+
+    /**
+     * Test for connectivity relations without a connectivity tag
+     */
+    @Test
+    public void testNoConnectivityTag() {
+        Relation relation = createDefaultTestRelation();
+        check.visit(relation);
+
+        Assert.assertEquals(0, check.getErrors().size());
+
+        relation.remove(CONNECTIVITY);
+        check.visit(relation);
+        Assert.assertEquals(1, check.getErrors().size());
+    }
+
+    /**
+     * Check for lanes that don't make sense
+     */
+    @Test
+    public void testMisMatchedLanes() {
+        Relation relation = createDefaultTestRelation();
+        check.visit(relation);
+        int expectedFailures = 0;
+
+        Assert.assertEquals(expectedFailures, check.getErrors().size());
+
+        relation.put(CONNECTIVITY, "45000:1");
+        check.visit(relation);
+        Assert.assertEquals(++expectedFailures, check.getErrors().size());
+
+        relation.put(CONNECTIVITY, "1:45000");
+        check.visit(relation);
+        Assert.assertEquals(++expectedFailures, check.getErrors().size());
+
+        relation.put(CONNECTIVITY, "1:1,2");
+        check.visit(relation);
+        Assert.assertEquals(expectedFailures, check.getErrors().size());
+
+        relation.put(CONNECTIVITY, "1:1,(2)");
+        check.visit(relation);
+        Assert.assertEquals(expectedFailures, check.getErrors().size());
+
+        relation.put(CONNECTIVITY, "1:1,(20000)");
+        check.visit(relation);
+        Assert.assertEquals(++expectedFailures, check.getErrors().size());
+    }
+
+    /**
+     * Check for bad roles (not from/via/to)
+     */
+    @Test
+    public void testForBadRole() {
+        Relation relation = createDefaultTestRelation();
+        check.visit(relation);
+        int expectedFailures = 0;
+
+        Assert.assertEquals(expectedFailures, check.getErrors().size());
+
+        for (int i = 0; i < relation.getMembers().size(); i++) {
+            String tRole = replaceMember(relation, i, "badRole");
+            check.visit(relation);
+            Assert.assertEquals(++expectedFailures, check.getErrors().size());
+            replaceMember(relation, i, tRole);
+            check.visit(relation);
+            Assert.assertEquals(expectedFailures, check.getErrors().size());
+        }
+    }
+
+    private String replaceMember(Relation relation, int index, String replacementRole) {
+        RelationMember relationMember = relation.getMember(index);
+        String currentRole = relationMember.getRole();
+        relation.removeMember(index);
+        relation.addMember(index, new RelationMember(replacementRole, relationMember.getMember()));
+        return currentRole;
+    }
+}
