code: def get_tile_geometry(path, origin_espg, tolerance=500):
      
  
      with rasterio.open(path) as src:
  
          # Get tile geometry
          b = src.bounds
          tile_shape = Polygon([(b[0], b[1]), (b[2], b[1]), (b[2], b[3]), (b[0], b[3]), (b[0], b[1])])
          tile_geojson = mapping(tile_shape)
  
          # read first band of the image
          image = src.read(1)
  
          # create a mask of zero values
          mask = image == 0.
  
          # generate shapes of the mask
          novalue_shape = shapes(image, mask=mask, transform=src.affine)
  
          # generate polygons using shapely
          novalue_shape = [Polygon(s['coordinates'][0]) for (s, v) in novalue_shape]
  
          if novalue_shape:
  
              # Make sure polygons are united
              # also simplify the resulting polygon
              union = cascaded_union(novalue_shape)
  
              # generates a geojson
              data_shape = tile_shape.difference(union)
  
              # If there are multipolygons, select the largest one
              if data_shape.geom_type == 'MultiPolygon':
                  areas = {p.area: i for i, p in enumerate(data_shape)}
                  largest = max(areas.keys())
                  data_shape = data_shape[areas[largest]]
  
              # if the polygon has interior rings, remove them
              if list(data_shape.interiors):
                  data_shape = Polygon(data_shape.exterior.coords)
  
              data_shape = data_shape.simplify(tolerance, preserve_topology=False)
              data_geojson = mapping(data_shape)
  
          else:
              data_geojson = tile_geojson
  
          # convert cooridnates to degrees
          return (to_latlon(tile_geojson, origin_espg), to_latlon(data_geojson, origin_espg))
docs: Calculate the data and tile geometry for sentinel-2 tiles