/**
     * Handle oneway streets, cycleways, and whatnot. See http://wiki.openstreetmap.org/wiki/Bicycle
     * for various scenarios, along with
     * http://wiki.openstreetmap.org/wiki/OSM_tags_for_routing#Oneway.
     *
     * @param end
     * @param start
     */
    private P2<PlainStreetEdge> getEdgesForStreet(
        IntersectionVertex start,
        IntersectionVertex end,
        OSMWay way,
        long startNode,
        StreetTraversalPermission permissions,
        LineString geometry) {
      // get geometry length in meters, irritatingly.
      Coordinate[] coordinates = geometry.getCoordinates();
      double d = 0;
      for (int i = 1; i < coordinates.length; ++i) {
        d += DistanceLibrary.distance(coordinates[i - 1], coordinates[i]);
      }

      LineString backGeometry = (LineString) geometry.reverse();

      Map<String, String> tags = way.getTags();

      if (permissions == StreetTraversalPermission.NONE) return new P2<PlainStreetEdge>(null, null);

      PlainStreetEdge street = null, backStreet = null;

      /*
       * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all)
       * bicycle rules: default: permissions;
       *
       * cycleway=dismount means walk your bike -- the engine will automatically try walking
       * bikes any time it is forbidden to ride them, so the only thing to do here is to
       * remove bike permissions
       *
       * oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these
       * permissions for bikes only
       *
       * now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been
       * set by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle
       *
       * bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like
       * oneway:bicycle=yes
       */

      String foot = way.getTag("foot");
      if ("yes".equals(foot) || "designated".equals(foot)) {
        permissions = permissions.add(StreetTraversalPermission.PEDESTRIAN);
      }

      if (OSMWithTags.isFalse(foot)) {
        permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN);
      }

      boolean forceBikes = false;
      String bicycle = way.getTag("bicycle");
      if ("yes".equals(bicycle) || "designated".equals(bicycle)) {
        permissions = permissions.add(StreetTraversalPermission.BICYCLE);
        forceBikes = true;
      }

      if (way.isTag("cycleway", "dismount") || "dismount".equals(bicycle)) {
        permissions = permissions.remove(StreetTraversalPermission.BICYCLE);
        if (forceBikes) {
          _log.warn(
              GraphBuilderAnnotation.register(graph, Variety.CONFLICTING_BIKE_TAGS, way.getId()));
        }
      }

      StreetTraversalPermission permissionsFront = permissions;
      StreetTraversalPermission permissionsBack = permissions;

      if (way.isTagTrue("oneway") || "roundabout".equals(tags.get("junction"))) {
        permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE_AND_CAR);
      }
      if (way.isTag("oneway", "-1")) {
        permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE_AND_CAR);
      }
      String oneWayBicycle = way.getTag("oneway:bicycle");
      if (OSMWithTags.isTrue(oneWayBicycle) || way.isTagFalse("bicycle:backwards")) {
        permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE);
      }
      if ("-1".equals(oneWayBicycle)) {
        permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE);
      }
      if (OSMWithTags.isFalse(oneWayBicycle) || way.isTagTrue("bicycle:backwards")) {
        if (permissions.allows(StreetTraversalPermission.BICYCLE)) {
          permissionsFront = permissionsFront.add(StreetTraversalPermission.BICYCLE);
          permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE);
        }
      }

      // any cycleway which is opposite* allows contraflow biking
      String cycleway = way.getTag("cycleway");
      String cyclewayLeft = way.getTag("cycleway:left");
      String cyclewayRight = way.getTag("cycleway:right");
      if ((cycleway != null && cycleway.startsWith("opposite"))
          || (cyclewayLeft != null && cyclewayLeft.startsWith("opposite"))
          || (cyclewayRight != null && cyclewayRight.startsWith("opposite"))) {

        permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE);
      }

      String access = way.getTag("access");
      boolean noThruTraffic =
          "destination".equals(access)
              || "private".equals(access)
              || "customers".equals(access)
              || "delivery".equals(access)
              || "forestry".equals(access)
              || "agricultural".equals(access);

      if (permissionsFront != StreetTraversalPermission.NONE) {
        street = getEdgeForStreet(start, end, way, startNode, d, permissionsFront, geometry, false);
        street.setNoThruTraffic(noThruTraffic);
      }
      if (permissionsBack != StreetTraversalPermission.NONE) {
        backStreet =
            getEdgeForStreet(end, start, way, startNode, d, permissionsBack, backGeometry, true);
        backStreet.setNoThruTraffic(noThruTraffic);
      }

      /* mark edges that are on roundabouts */
      if ("roundabout".equals(tags.get("junction"))) {
        if (street != null) street.setRoundabout(true);
        if (backStreet != null) backStreet.setRoundabout(true);
      }

      return new P2<PlainStreetEdge>(street, backStreet);
    }
    private StreetTraversalPermission getPermissionsForEntity(
        OSMWithTags entity, StreetTraversalPermission def) {
      Map<String, String> tags = entity.getTags();
      StreetTraversalPermission permission = null;

      String highway = tags.get("highway");
      String cycleway = tags.get("cycleway");
      String access = tags.get("access");
      String motorcar = tags.get("motorcar");
      String bicycle = tags.get("bicycle");
      String foot = tags.get("foot");

      /*
       * Only a few tags are examined here, because we only care about modes supported by OTP
       * (wheelchairs are not of concern here)
       *
       * Only a few values are checked for, all other values are presumed to be permissive (=>
       * This may not be perfect, but is closer to reality, since most people don't follow the
       * rules perfectly ;-)
       */
      if (access != null) {
        if ("no".equals(access) || "license".equals(access)) {
          // this can actually be overridden
          permission = StreetTraversalPermission.NONE;
          if (entity.doesTagAllowAccess("motorcar")) {
            permission = permission.add(StreetTraversalPermission.CAR);
          }
          if (entity.doesTagAllowAccess("bicycle")) {
            permission = permission.add(StreetTraversalPermission.BICYCLE);
          }
          if (entity.doesTagAllowAccess("foot")) {
            permission = permission.add(StreetTraversalPermission.PEDESTRIAN);
          }
        } else {
          permission = def;
        }
      } else if (motorcar != null || bicycle != null || foot != null) {
        permission = def;
      }

      if (motorcar != null) {
        if ("no".equals(motorcar) || "license".equals(motorcar)) {
          permission = permission.remove(StreetTraversalPermission.CAR);
        } else {
          permission = permission.add(StreetTraversalPermission.CAR);
        }
      }

      if (bicycle != null) {
        if ("no".equals(bicycle) || "license".equals(bicycle)) {
          permission = permission.remove(StreetTraversalPermission.BICYCLE);
        } else {
          permission = permission.add(StreetTraversalPermission.BICYCLE);
        }
      }

      if (foot != null) {
        if ("no".equals(foot) || "license".equals(foot)) {
          permission = permission.remove(StreetTraversalPermission.PEDESTRIAN);
        } else {
          permission = permission.add(StreetTraversalPermission.PEDESTRIAN);
        }
      }

      if (highway != null) {
        if ("construction".equals(highway)) {
          permission = StreetTraversalPermission.NONE;
        }
      } else {
        if ("construction".equals(cycleway)) {
          permission = StreetTraversalPermission.NONE;
        }
      }

      if (permission == null) return def;

      return permission;
    }