/**
   * Get polygons covering the components of the graph. The largest component (in terms of number of
   * nodes) will not overlap any other components (it will have holes); the others may overlap each
   * other.
   *
   * @param modes
   * @return
   */
  @Secured({"ROLE_USER"})
  @GET
  @Path("/polygons")
  @Produces({MediaType.APPLICATION_JSON})
  public GraphComponentPolygons getComponentPolygons(
      @DefaultValue("TRANSIT,WALK") @QueryParam("modes") TraverseModeSet modes,
      @QueryParam(RequestInf.DATE) String date,
      @QueryParam(RequestInf.TIME) String time,
      @DefaultValue("") @QueryParam(RequestInf.BANNED_ROUTES) String bannedRoutes) {

    TraverseOptions options = new TraverseOptions(modes);
    options.bannedRoutes = new HashSet<RouteSpec>();
    if (bannedRoutes.length() > 0) {
      for (String element : bannedRoutes.split(",")) {
        String[] routeSpec = element.split("_", 2);
        if (routeSpec.length != 2) {
          throw new IllegalArgumentException("AgencyId or routeId not set in bannedRoutes list");
        }
        options.bannedRoutes.add(new RouteSpec(routeSpec[0], routeSpec[1]));
      }
    }

    long dateTime = DateUtils.toDate(date, time).getTime();
    if (cachedPolygons == null || dateTime != cachedDateTime || !options.equals(cachedOptions)) {
      cachedOptions = options;
      cachedDateTime = dateTime;
      Graph graph = graphService.getGraph();
      if (graphService.getCalendarService() != null) {
        options.setCalendarService(graphService.getCalendarService());
      }
      options.setServiceDays(dateTime, graph.getAgencyIds());
      cachedPolygons = AnalysisUtils.getComponentPolygons(graph, options, dateTime);
    }

    GraphComponentPolygons out = new GraphComponentPolygons();
    out.components = new ArrayList<GraphComponent>();

    for (Geometry geometry : cachedPolygons) {
      GraphComponent component = new GraphComponent();
      component.polygon = geometry;
      out.components.add(component);
    }

    return out;
  }
 /**
  * Cache ServiceDay objects representing which services are running yesterday, today, and tomorrow
  * relative to the search time. This information is very heavily used (at every transit boarding)
  * and Date operations were identified as a performance bottleneck. Must be called after the
  * TraverseOptions already has a CalendarService set.
  */
 public void setServiceDays() {
   final long SEC_IN_DAY = 60 * 60 * 24;
   final long time = opt.getSecondsSinceEpoch();
   this.serviceDays = new ArrayList<ServiceDay>(3);
   if (calendarService == null
       && graph.getCalendarService() != null
       && (opt.getModes() == null || opt.getModes().contains(TraverseMode.TRANSIT))) {
     LOG.warn("RoutingContext has no CalendarService. Transit will never be boarded.");
     return;
   }
   // This should be a valid way to find yesterday and tomorrow,
   // since DST changes more than one hour after midnight in US/EU.
   // But is this true everywhere?
   for (String agency : graph.getAgencyIds()) {
     addIfNotExists(
         this.serviceDays, new ServiceDay(graph, time - SEC_IN_DAY, calendarService, agency));
     addIfNotExists(this.serviceDays, new ServiceDay(graph, time, calendarService, agency));
     addIfNotExists(
         this.serviceDays, new ServiceDay(graph, time + SEC_IN_DAY, calendarService, agency));
   }
 }