public static List<WrappedGTFSEntity<Pattern>> apex(DataFetchingEnvironment env) {
    Collection<FeedSource> feeds;

    List<String> feedId = (List<String>) env.getArgument("feed_id");
    feeds = ApiMain.getFeedSources(feedId);
    Map<String, Object> args = env.getArguments();
    List<WrappedGTFSEntity<Pattern>> patterns = new ArrayList<>();

    for (FeedSource fs : feeds) {
      if (env.getArgument("pattern_id") != null) {
        List<String> patternId = (List<String>) env.getArgument("pattern_id");
        patternId
            .stream()
            .filter(fs.feed.patterns::containsKey)
            .map(fs.feed.patterns::get)
            .map(pattern -> new WrappedGTFSEntity(fs.id, pattern))
            .forEach(patterns::add);
      } else if (env.getArgument("route_id") != null) {
        List<String> routeId = (List<String>) env.getArgument("route_id");
        fs.feed
            .patterns
            .values()
            .stream()
            .filter(p -> routeId.contains(p.route_id))
            .map(pattern -> new WrappedGTFSEntity(fs.id, pattern))
            .forEach(patterns::add);
      }
      // get patterns by lat/lon/radius
      else if (argumentDefined(env, "lat") && argumentDefined(env, "lon")) {
        Double lat = (Double) args.get("lat");
        Double lon = (Double) args.get("lon");
        Double radius = args.get("radius") == null ? DEFAULT_RADIUS : (Double) args.get("radius");
        Coordinate latLng = new Coordinate(lon, lat);
        Envelope searchEnvelope = GeomUtil.getBoundingBox(latLng, radius);

        List<Pattern> results = fs.routeIndex.query(searchEnvelope);
        results
            .stream()
            .map(pattern -> new WrappedGTFSEntity(fs.id, pattern))
            .forEach(patterns::add);
      }
      // get patterns by bounding box
      else if (argumentDefined(env, "min_lat")
          && argumentDefined(env, "max_lat")
          && argumentDefined(env, "min_lon")
          && argumentDefined(env, "max_lon")) {
        Coordinate maxCoordinate =
            new Coordinate((Double) args.get("max_lon"), (Double) args.get("max_lat"));
        Coordinate minCoordinate =
            new Coordinate((Double) args.get("min_lon"), (Double) args.get("min_lat"));
        Envelope searchEnvelope = new Envelope(maxCoordinate, minCoordinate);

        List<Pattern> results = (List<Pattern>) fs.routeIndex.query(searchEnvelope);
        results
            .stream()
            .filter(p -> p != null)
            .map(pattern -> new WrappedGTFSEntity(fs.id, pattern))
            .forEach(patterns::add);
      }
    }

    return patterns;
  }
  public static Object getRoutes(Request req, Response res) {

    List<Route> routes = new ArrayList<>();
    List<String> feeds = new ArrayList();

    if (req.queryParams("feed") != null) {

      for (String feedId : req.queryParams("feed").split(",")) {
        if (ApiMain.getFeedSource(feedId) != null) {
          feeds.add(feedId);
        }
      }
      if (feeds.size() == 0) {
        halt(404, "Must specify valid feed id.");
      }
      // If feed is only param.
      else if (req.queryParams().size() == 1 && req.params("id") == null) {
        for (String feedId : req.queryParams("feed").split(",")) {
          FeedSource feedSource = ApiMain.getFeedSource(feedId);
          if (feedSource != null) routes.addAll(feedSource.feed.routes.values());
        }
        return routes;
      }
    } else {
      //            res.body("Must specify valid feed id.");
      //            return "Must specify valid feed id.";
      halt(404, "Must specify valid feed id.");
    }

    // get specific route
    if (req.params("id") != null) {
      Route r = ApiMain.getFeedSource(feeds.get(0)).feed.routes.get(req.params("id"));
      if (r != null) // && currentUser(req).hasReadPermission(s.projectId))
      return r;
      else halt(404, "Route " + req.params("id") + " not found");
    }
    // bounding box
    else if (req.queryParams("max_lat") != null
        && req.queryParams("max_lon") != null
        && req.queryParams("min_lat") != null
        && req.queryParams("min_lon") != null) {
      Coordinate maxCoordinate =
          new Coordinate(
              Double.valueOf(req.queryParams("max_lon")),
              Double.valueOf(req.queryParams("max_lat")));
      Coordinate minCoordinate =
          new Coordinate(
              Double.valueOf(req.queryParams("min_lon")),
              Double.valueOf(req.queryParams("min_lat")));
      Envelope searchEnvelope = new Envelope(maxCoordinate, minCoordinate);
      for (String feedId : feeds) {
        // TODO: these are actually patterns being returned, NOT routes
        List<Route> searchResults = ApiMain.getFeedSource(feedId).routeIndex.query(searchEnvelope);
        routes.addAll(searchResults);
      }
      return routes;
    }
    // lat lon + radius
    else if (req.queryParams("lat") != null && req.queryParams("lon") != null) {
      Coordinate latLon =
          new Coordinate(
              Double.valueOf(req.queryParams("lon")), Double.valueOf(req.queryParams("lat")));
      if (req.queryParams("radius") != null) {
        RoutesController.radius = Double.valueOf(req.queryParams("radius"));
      }
      Envelope searchEnvelope = GeomUtil.getBoundingBox(latLon, radius);

      for (String feedId : feeds) {
        List<Route> searchResults = ApiMain.getFeedSource(feedId).routeIndex.query(searchEnvelope);
        routes.addAll(searchResults);
      }
      return routes;
    } else if (req.queryParams("name") != null) {
      System.out.println(req.queryParams("name"));

      for (String feedId : feeds) {
        System.out.println("looping feed: " + feedId);

        // Check if feed is specified in feed sources requested
        // TODO: Check if user has access to feed source? (Put this in the before call.)
        //                if (Arrays.asList(feeds).contains(entry.getKey())) {
        System.out.println("checking feed: " + feedId);

        // search query must be in upper case to match radix tree keys
        Iterable<Route> searchResults =
            ApiMain.getFeedSource(feedId)
                .routeTree
                .getValuesForKeysContaining(req.queryParams("name").toUpperCase());
        for (Route route : searchResults) {
          routes.add(route);
        }
      }

      return routes;
    }
    // query for stop_id (i.e., get all routes that operate along patterns for a given stop)
    else if (req.queryParams("stop") != null) {
      return getRoutesForStop(req, feeds);
    }

    return null;
  }