public FilterPredicate bboxFilter(MessageType schema, Bounds bounds) {
      if (!bounds.isWorld()) {
        var covering = covering();
        // if covering metadata missing, use default bbox:{xmin,xmax,ymin,ymax}
        if (covering == null) {
          if (hasNumericField(schema, "bbox.xmin") &&
            hasNumericField(schema, "bbox.xmax") &&
            hasNumericField(schema, "bbox.ymin") &&
            hasNumericField(schema, "bbox.ymax")) {
            covering = new GeoParquetMetadata.Covering(new GeoParquetMetadata.CoveringBbox(
              List.of("bbox.xmin"),
              List.of("bbox.ymin"),
              List.of("bbox.xmax"),
              List.of("bbox.ymax")
            ));
          } else if (hasNumericField(schema, "bbox", "xmin") &&
            hasNumericField(schema, "bbox", "xmax") &&
            hasNumericField(schema, "bbox", "ymin") &&
            hasNumericField(schema, "bbox", "ymax")) {
            covering = new GeoParquetMetadata.Covering(new GeoParquetMetadata.CoveringBbox(
              List.of("bbox", "xmin"),
              List.of("bbox", "ymin"),
              List.of("bbox", "xmax"),
              List.of("bbox", "ymax")
            ));
          }
        }
        if (covering != null) {
          var latLonBounds = bounds.latLon();
          // TODO apply projection
          var coveringBbox = covering.bbox();
          var coordinateType =
            schema.getColumnDescription(coveringBbox.xmax().toArray(String[]::new))
              .getPrimitiveType()
              .getPrimitiveTypeName();
          BiFunction<List<String>, Number, FilterPredicate> gtEq = switch (coordinateType) {
            case DOUBLE -> (p, v) -> FilterApi.gtEq(Filters.doubleColumn(p), v.doubleValue());
            case FLOAT -> (p, v) -> FilterApi.gtEq(Filters.floatColumn(p), v.floatValue());
            default -> throw new UnsupportedOperationException();
          };
          BiFunction<List<String>, Number, FilterPredicate> ltEq = switch (coordinateType) {
            case DOUBLE -> (p, v) -> FilterApi.ltEq(Filters.doubleColumn(p), v.doubleValue());
            case FLOAT -> (p, v) -> FilterApi.ltEq(Filters.floatColumn(p), v.floatValue());
            default -> throw new UnsupportedOperationException();
          };
          return FilterApi.and(
            FilterApi.and(
              gtEq.apply(coveringBbox.xmax(), latLonBounds.getMinX()),
              ltEq.apply(coveringBbox.xmin(), latLonBounds.getMaxX())
            ),
            FilterApi.and(
              gtEq.apply(coveringBbox.ymax(), latLonBounds.getMinY()),
              ltEq.apply(coveringBbox.ymin(), latLonBounds.getMaxY())
            )
          );
        }
      }
      return null;
    }
Returns a parquet filter that filters records read to only those where the covering bbox overlaps {@code bounds}
or null if unable to infer that from the metadata.
<p>
If covering bbox metadata is missing from geoparquet metadata, it will try to use bbox.xmin, bbox.xmax,
bbox.ymin, and bbox.ymax if present.
planetiler-core/src/main/java/com/onthegomap/planetiler/reader/parquet/GeoParquetMetadata.java