@Secured({"ROLE_USER"})
  @GET
  @Path("/annotations")
  @Produces({MediaType.APPLICATION_JSON})
  public Object getAnnotations() {
    Graph graph = graphService.getGraph();
    List<GraphBuilderAnnotation> builderAnnotations = graph.getBuilderAnnotations();

    List<Annotation> out = new ArrayList<Annotation>();
    for (GraphBuilderAnnotation annotation : builderAnnotations) {
      Annotation outAnnotation = new Annotation();
      out.add(outAnnotation);
      outAnnotation.annotation = annotation.getVariety().name();
      Collection<Object> referencedObjects = annotation.getReferencedObjects();
      for (Object object : referencedObjects) {
        AnnotationObject annotationObj = new AnnotationObject();
        applyObjectToAnnotation(graph, annotationObj, object);
        outAnnotation.addObject(annotationObj);
      }
    }

    Annotations annotations = new Annotations();
    annotations.annotations = out;
    return annotations;
  }
    /**
     * The safest bike lane should have a safety weight no lower than the time weight of a flat
     * street. This method divides the safety lengths by the length ratio of the safest street,
     * ensuring this property.
     *
     * @param graph
     */
    private void applyBikeSafetyFactor(Graph graph) {
      _log.info(
          GraphBuilderAnnotation.register(
              graph,
              Variety.GRAPHWIDE,
              "Multiplying all bike safety values by " + (1 / bestBikeSafety)));
      HashSet<Edge> seenEdges = new HashSet<Edge>();
      for (Vertex vertex : graph.getVertices()) {
        for (Edge e : vertex.getOutgoing()) {
          if (!(e instanceof PlainStreetEdge)) {
            continue;
          }
          PlainStreetEdge pse = (PlainStreetEdge) e;

          if (!seenEdges.contains(e)) {
            seenEdges.add(e);
            pse.setBicycleSafetyEffectiveLength(
                pse.getBicycleSafetyEffectiveLength() / bestBikeSafety);
          }
        }
        for (Edge e : vertex.getIncoming()) {
          if (!(e instanceof PlainStreetEdge)) {
            continue;
          }
          PlainStreetEdge pse = (PlainStreetEdge) e;

          if (!seenEdges.contains(e)) {
            seenEdges.add(e);
            pse.setBicycleSafetyEffectiveLength(
                pse.getBicycleSafetyEffectiveLength() / bestBikeSafety);
          }
        }
      }
    }
    /**
     * 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);
    }
    /**
     * Handle turn restrictions
     *
     * @param relation
     */
    private void processRestriction(OSMRelation relation) {
      long from = -1, to = -1, via = -1;
      for (OSMRelationMember member : relation.getMembers()) {
        String role = member.getRole();
        if (role.equals("from")) {
          from = member.getRef();
        } else if (role.equals("to")) {
          to = member.getRef();
        } else if (role.equals("via")) {
          via = member.getRef();
        }
      }
      if (from == -1 || to == -1 || via == -1) {
        _log.warn(
            GraphBuilderAnnotation.register(graph, Variety.TURN_RESTRICTION_BAD, relation.getId()));
        return;
      }

      Set<TraverseMode> modes = EnumSet.of(TraverseMode.BICYCLE, TraverseMode.CAR);
      String exceptModes = relation.getTag("except");
      if (exceptModes != null) {
        for (String m : exceptModes.split(";")) {
          if (m.equals("motorcar")) {
            modes.remove(TraverseMode.CAR);
          } else if (m.equals("bicycle")) {
            modes.remove(TraverseMode.BICYCLE);
            _log.warn(
                GraphBuilderAnnotation.register(
                    graph, Variety.TURN_RESTRICTION_EXCEPTION, via, from));
          }
        }
      }
      modes = TraverseMode.internSet(modes);

      TurnRestrictionTag tag;
      if (relation.isTag("restriction", "no_right_turn")) {
        tag = new TurnRestrictionTag(via, TurnRestrictionType.NO_TURN);
      } else if (relation.isTag("restriction", "no_left_turn")) {
        tag = new TurnRestrictionTag(via, TurnRestrictionType.NO_TURN);
      } else if (relation.isTag("restriction", "no_straight_on")) {
        tag = new TurnRestrictionTag(via, TurnRestrictionType.NO_TURN);
      } else if (relation.isTag("restriction", "no_u_turn")) {
        tag = new TurnRestrictionTag(via, TurnRestrictionType.NO_TURN);
      } else if (relation.isTag("restriction", "only_straight_on")) {
        tag = new TurnRestrictionTag(via, TurnRestrictionType.ONLY_TURN);
      } else if (relation.isTag("restriction", "only_right_turn")) {
        tag = new TurnRestrictionTag(via, TurnRestrictionType.ONLY_TURN);
      } else if (relation.isTag("restriction", "only_left_turn")) {
        tag = new TurnRestrictionTag(via, TurnRestrictionType.ONLY_TURN);
      } else {
        _log.warn(
            GraphBuilderAnnotation.register(
                graph, Variety.TURN_RESTRICTION_UNKNOWN, relation.getTag("restriction")));
        return;
      }
      TurnRestriction restriction = new TurnRestriction();
      restriction.type = tag.type;
      restriction.modes = modes;
      turnRestrictionsByTag.put(tag, restriction);

      MapUtils.addToMapList(turnRestrictionsByFromWay, from, tag);
      MapUtils.addToMapList(turnRestrictionsByToWay, to, tag);
    }
 public static String register(Graph graph, Variety variety, Object... refs) {
   GraphBuilderAnnotation gba = new GraphBuilderAnnotation(variety, refs);
   graph.addBuilderAnnotation(gba);
   LOG.trace("Registering {}", gba);
   return gba.getMessage() + " (annotation registered)";
 }