def log_jaccard(im_id: str, cls: int,
true_mask: np.ndarray, mask: np.ndarray, poly_mask: np.ndarray,
true_poly: MultiPolygon, poly: MultiPolygon,
valid_polygons=False):
assert len(mask.shape) == 2
pixel_jc = utils.mask_tp_fp_fn(mask, true_mask, 0.5)
if valid_polygons:
if not true_poly.is_valid:
true_poly = utils.to_multipolygon(true_poly.buffer(0))
if not poly.is_valid:
poly = utils.to_multipolygon(poly.buffer(0))
tp = true_poly.intersection(poly).area
fn = true_poly.difference(poly).area
fp = poly.difference(true_poly).area
poly_jc = tp, fp, fn
else:
poly_jc = utils.mask_tp_fp_fn(poly_mask, true_mask, 0.5)
logger.info('{} cls-{} pixel jaccard: {:.5f}, polygon jaccard: {:.5f}'
.format(im_id, cls, jaccard(pixel_jc), jaccard(poly_jc)))
return pixel_jc, poly_jc
python类MultiPolygon()的实例源码
def polygonal_min(self, geometry, data_type):
"""Finds the min value for each band that is contained within the given geometry.
Args:
geometry (shapely.geometry.Polygon or shapely.geometry.MultiPolygon or bytes): A
Shapely ``Polygon`` or ``MultiPolygon`` that represents the area where the summary
should be computed; or a WKB representation of the geometry.
data_type (type): The type of the values within the rasters. Can either be int or
float.
Returns:
[int] or [float] depending on ``data_type``.
Raises:
TypeError: If ``data_type`` is not an int or float.
"""
if data_type is int:
return self._process_polygonal_summary(geometry, self.srdd.polygonalMin)
elif data_type is float:
return self._process_polygonal_summary(geometry, self.srdd.polygonalMinDouble)
else:
raise TypeError("data_type must be either int or float.")
def polygonal_max(self, geometry, data_type):
"""Finds the max value for each band that is contained within the given geometry.
Args:
geometry (shapely.geometry.Polygon or shapely.geometry.MultiPolygon or bytes): A
Shapely ``Polygon`` or ``MultiPolygon`` that represents the area where the summary
should be computed; or a WKB representation of the geometry.
data_type (type): The type of the values within the rasters. Can either be int or
float.
Returns:
[int] or [float] depending on ``data_type``.
Raises:
TypeError: If ``data_type`` is not an int or float.
"""
if data_type is int:
return self._process_polygonal_summary(geometry, self.srdd.polygonalMax)
elif data_type is float:
return self._process_polygonal_summary(geometry, self.srdd.polygonalMaxDouble)
else:
raise TypeError("data_type must be either int or float.")
def polygonal_sum(self, geometry, data_type):
"""Finds the sum of all of the values in each band that are contained within the given geometry.
Args:
geometry (shapely.geometry.Polygon or shapely.geometry.MultiPolygon or bytes): A
Shapely ``Polygon`` or ``MultiPolygon`` that represents the area where the summary
should be computed; or a WKB representation of the geometry.
data_type (type): The type of the values within the rasters. Can either be int or
float.
Returns:
[int] or [float] depending on ``data_type``.
Raises:
TypeError: If ``data_type`` is not an int or float.
"""
if data_type is int:
return self._process_polygonal_summary(geometry, self.srdd.polygonalSum)
elif data_type is float:
return self._process_polygonal_summary(geometry, self.srdd.polygonalSumDouble)
else:
raise TypeError("data_type must be either int or float.")
simplify_sub.py 文件源码
项目:kaggle-dstl-satellite-imagery-feature-detection
作者: u1234x1234
项目源码
文件源码
阅读 19
收藏 0
点赞 0
评论 0
def func(dat):
image_id, class_id, sh = dat
sh = wkt.loads(sh)
# print(len(sh.wkt))
# print(len(sh.wkt))
# if not sh.is_valid:
# if not isinstance(sh, MultiPolygon):
# sh = MultiPolygon([sh])
# sh = clip_poly(sh)
# if not isinstance(sh, MultiPolygon):
# sh = MultiPolygon([sh])
# sh = clip_poly(sh)
sh = sh.buffer(0.0000000000001)
sh = sh.simplify(0.00000001, preserve_topology=True)
# sh = MultiPolygon([x.buffer(0) for x in sh])
# sh = ops.cascaded_union(sh)
if not sh.is_valid:
print(image_id, class_id)
# qwe
pol = sh.wkt
# pol = wkt.dumps(sh, rounding_precision=8)
return image_id, class_id, pol
utils.py 文件源码
项目:kaggle-dstl-satellite-imagery-feature-detection
作者: u1234x1234
项目源码
文件源码
阅读 18
收藏 0
点赞 0
评论 0
def polygonize_cv(mask, epsilon=1., min_area=10.):
contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_TC89_KCOS)
# create approximate contours to have reasonable submission size
approx_contours = [cv2.approxPolyDP(cnt, epsilon, True)
for cnt in contours]
approx_contours = contours
if not contours:
return MultiPolygon()
# now messy stuff to associate parent and child contours
cnt_children = defaultdict(list)
child_contours = set()
assert hierarchy.shape[0] == 1
# http://docs.opencv.org/3.1.0/d9/d8b/tutorial_py_contours_hierarchy.html
for idx, (_, _, _, parent_idx) in enumerate(hierarchy[0]):
if parent_idx != -1:
child_contours.add(idx)
cnt_children[parent_idx].append(approx_contours[idx])
# create actual polygons filtering by area (removes artifacts)
all_polygons = []
for idx, cnt in enumerate(approx_contours):
if idx not in child_contours and cv2.contourArea(cnt) >= min_area:
assert cnt.shape[1] == 1
poly = Polygon(
shell=cnt[:, 0, :],
holes=[c[:, 0, :] for c in cnt_children.get(idx, [])
if cv2.contourArea(c) >= min_area])
all_polygons.append(poly)
# approximating polygons might have created invalid ones, fix them
all_polygons = MultiPolygon(all_polygons)
if not all_polygons.is_valid:
all_polygons = all_polygons.buffer(0)
# Sometimes buffer() converts a simple Multipolygon to just a Polygon,
# need to keep it a Multi throughout
if all_polygons.type == 'Polygon':
all_polygons = MultiPolygon([all_polygons])
return all_polygons
def Import_reticule(IDT_data,reticule_size,reticule_filename):
all_points = SVGT.read_path_from_svg(reticule_filename)
reticule =[]
for points in all_points:
reticule.append(shapely_geom.Polygon(points.T))
reticule = shapely_geom.MultiPolygon(reticule)
outbox = reticule.bounds
dx = outbox[2]-outbox[0]
dy = outbox[3]-outbox[1]
d = max([dx,dy])
x0 = outbox[0]+dx/2
y0 = outbox[1]+dy/2
factor = 2*reticule_size/d
reticule = shapely_affinity.translate(reticule, xoff=-x0, yoff=-y0)
reticule = shapely_affinity.scale(reticule, xfact = factor, yfact= factor, origin=(0,0,0))
IDT_data['reticule'] = reticule
def Import_SVG_route(IDT_data):
route_filename = IDT_data['svg_route']
all_points = SVGT.read_path_from_svg(route_filename)
route =[]
for points in all_points:
route.append(shapely_geom.Polygon(points.T))
route = shapely_geom.MultiPolygon(route)
#outbox = route.bounds
#dx = outbox[2]-outbox[0]
#dy = outbox[3]-outbox[1]
#x0 = outbox[0]+dx/2
#y0 = outbox[1]+dy/2
x0svg = 4000
y0svg = 4000
factor = 1e-5;
route = shapely_affinity.translate(route, xoff=-x0svg, yoff=-y0svg)
route = shapely_affinity.scale(route, xfact = factor, yfact= factor, origin=(0,0,0))
IDT_data['route'] = route
def merge_to_multi_polygon(feature_collection: str, dissolve: bool) -> geojson.MultiPolygon:
"""
Merge all geometries to a single multipolygon
:param feature_collection: geojson feature collection str containing features
:param dissolve: flag for wther to to dissolve internal boundaries.
:return: geojson.MultiPolygon
"""
parsed_geojson = GridService._to_shapely_geometries(json.dumps(feature_collection))
multi_polygon = GridService._convert_to_multipolygon(parsed_geojson)
if dissolve:
multi_polygon = GridService._dissolve(multi_polygon)
aoi_multi_polygon_geojson = geojson.loads(json.dumps(mapping(multi_polygon)))
# validate the geometry
if type(aoi_multi_polygon_geojson) is not geojson.MultiPolygon:
raise InvalidGeoJson('Area Of Interest: geometry must be a MultiPolygon')
is_valid_geojson = geojson.is_valid(aoi_multi_polygon_geojson)
if is_valid_geojson['valid'] == 'no':
raise InvalidGeoJson(f"Area of Interest: Invalid MultiPolygon - {is_valid_geojson['message']}")
return aoi_multi_polygon_geojson
def _update_feature(clip_to_aoi: bool, feature: dict, new_shape) -> dict:
"""
Updates the feature with the new shape, and splittable property
:param clip_to_aoi: value for feature's splittable property
:param feature: feature to be updated
:param new_shape: new shape to use for feature
:return:
"""
if clip_to_aoi:
# update the feature with the clipped shape
if new_shape.geom_type == 'Polygon':
# shapely may return a POLYGON rather than a MULTIPOLYGON if there is just one intersection area
new_shape = MultiPolygon([new_shape])
feature['geometry'] = mapping(new_shape)
feature['properties']['x'] = None
feature['properties']['y'] = None
feature['properties']['zoom'] = None
feature['properties']['splittable'] = False
return feature
def _convert_to_multipolygon(features: list) -> MultiPolygon:
"""
converts a list of (multi)polygon geometries to one single multipolygon
:param features:
:return:
"""
rings = []
for feature in features:
if isinstance(feature.geometry, MultiPolygon):
rings = rings + [geom for geom in feature.geometry.geoms]
else:
rings = rings + [feature.geometry]
geometry = MultiPolygon(rings)
# Downsample 3D -> 2D
if geometry.has_z:
geometry = shapely.ops.transform(GridService._to_2d, geometry)
wkt2d = geometry.wkt
geom2d = shapely.wkt.loads(wkt2d)
return geom2d
def polygonal_mean(self, geometry):
"""Finds the mean of all of the values for each band that are contained within the given geometry.
Args:
geometry (shapely.geometry.Polygon or shapely.geometry.MultiPolygon or bytes): A
Shapely ``Polygon`` or ``MultiPolygon`` that represents the area where the summary
should be computed; or a WKB representation of the geometry.
Returns:
[float]
"""
return self._process_polygonal_summary(geometry, self.srdd.polygonalMean)
simplify_sub.py 文件源码
项目:kaggle-dstl-satellite-imagery-feature-detection
作者: u1234x1234
项目源码
文件源码
阅读 24
收藏 0
点赞 0
评论 0
def clip_poly(polys):
new_polys = []
for poly in polys:
x, y = poly.exterior.coords.xy
x = np.clip(x, 0.00001, 0.9999)
y = np.clip(y, -0.9999, 0.09001)
new_polys.append(Polygon(shell=zip(x, y)))
return MultiPolygon(new_polys)
utils.py 文件源码
项目:kaggle-dstl-satellite-imagery-feature-detection
作者: u1234x1234
项目源码
文件源码
阅读 22
收藏 0
点赞 0
评论 0
def polygonize(im):
assert len(im.shape) == 2
shapes = features.shapes(im)
polygons = []
for i, shape in enumerate(shapes):
# if i % 10000 == 0:
# print(i)
if shape[1] == 0:
continue
polygons.append(geometry.shape(shape[0]))
# polygons = [geometry.shape(shape[0]) for shape in shapes if shape[1] > 0]
mp = geometry.MultiPolygon(polygons)
return mp
def to_Trajectory(self, inter):
if (inter.has_z == False):
raise Exception('O objeto precisa ser uma geometria com um atributo z')
if isinstance(inter, MultiLineString) or isinstance(inter, MultiPolygon):
array_traj = []
for multi in inter:
array_traj.append(self.__set_data__(multi))
return array_traj
return self.__set_data__(inter)
def _handle_multi(df):
"""
Helper function for _clean_geometry(). Handles Multipolygons
Parameters
----------
df: pandas.DataFrame
Returns
-------
"""
assert isinstance(df, pandas.DataFrame)
geometry_col = 'geometry'
df_row = df.copy()
assert isinstance(df_row, pandas.DataFrame)
row_list = []
x = df_row[geometry_col].iloc[0]
if isinstance(x, MultiPolygon):
for y in x.geoms:
df_row_temp = df_row
assert isinstance(df_row_temp, pandas.DataFrame)
df_row_temp[geometry_col] = y
row_list.append(df_row_temp)
df_multi_row = pandas.concat(row_list, ignore_index=True)
assert isinstance(df_multi_row, pandas.DataFrame)
df_to_return = df_multi_row
assert isinstance(df_to_return, pandas.DataFrame)
assert all(isinstance(geometry, shapely.geometry.Polygon)
for geometry in df_to_return[geometry_col].values)
return df_to_return
elif isinstance(x, shapely.geometry.polygon.Polygon):
return df_row
else:
return pandas.DataFrame()
def check_mask(self):
if len(set(self.mask.type)
.intersection({"Polygon", "MultiPolygon"})) > 0:
if self.mask.crs and self.mask.crs is not self.proj_to_use:
self.mask.to_crs(self.proj_to_use, inplace=True)
else:
self.mask.crs = self.proj_to_use
self.use_mask = True
else:
self.use_mask = False
submission.py 文件源码
项目:Dstl-Satellite-Imagery-Feature-Detection
作者: DeepVoltaire
项目源码
文件源码
阅读 23
收藏 0
点赞 0
评论 0
def mask_to_polygons(mask, epsilon=1, min_area=1.):
"""
Create a Multipolygon from a mask of 0-1 pixels.
"""
# find contours of mask of pixels
image, contours, hierarchy = cv2.findContours(
((mask == 1) * 255).astype(np.uint8),
cv2.RETR_CCOMP, cv2.CHAIN_APPROX_TC89_KCOS)
# create approximate contours to have reasonable submission size
approx_contours = [cv2.approxPolyDP(cnt, epsilon, True)
for cnt in contours]
if not contours:
return MultiPolygon()
# now messy stuff to associate parent and child contours
cnt_children = defaultdict(list)
child_contours = set()
assert hierarchy.shape[0] == 1
# http://docs.opencv.org/3.1.0/d9/d8b/tutorial_py_contours_hierarchy.html
for idx, (_, _, _, parent_idx) in enumerate(hierarchy[0]):
if parent_idx != -1:
child_contours.add(idx)
cnt_children[parent_idx].append(approx_contours[idx])
# create actual polygons filtering by area (removes artifacts)
all_polygons = []
for idx, cnt in enumerate(approx_contours):
if idx not in child_contours and cv2.contourArea(cnt) >= min_area:
assert cnt.shape[1] == 1
poly = Polygon(
shell=cnt[:, 0, :],
holes=[c[:, 0, :] for c in cnt_children.get(idx, [])
if cv2.contourArea(c) >= min_area])
all_polygons.append(poly)
# approximating polygons might have created invalid ones, fix them
all_polygons = MultiPolygon(all_polygons)
if not all_polygons.is_valid:
all_polygons = all_polygons.buffer(0)
# Sometimes buffer() converts a simple Multipolygon to just a Polygon,
# need to keep it a Multi throughout
if all_polygons.type == 'Polygon':
all_polygons = MultiPolygon([all_polygons])
return all_polygons
def IDT_union(IDT_data):
IDT = IDT_data['IDT']
branch0 = IDT_data['electrodes']['elect0']
branch1 = IDT_data['electrodes']['elect1']
removal0 = IDT_data['removal']['removal0']
removal1 = IDT_data['removal']['removal1']
#reticule = IDT_data['reticule']
removal_center = IDT_data['removal']['removal_center']
for polygon in IDT[0]:
branch0 = branch0.union(polygon)
branch0 = branch0.difference(removal1)
if 'Hole_elect0' in IDT_data['removal'].keys():
branch0 = branch0.difference(IDT_data['removal']['Hole_elect0'])
branch0 = branch0.difference(removal_center)
for polygon in IDT[1]:
branch1 = branch1.union(polygon)
branch1 = branch1.difference(removal0)
if 'Hole_elect1' in IDT_data['removal'].keys():
branch1 = branch1.difference(IDT_data['removal']['Hole_elect1'])
branch1 = branch1.difference(removal_center)
IDT_data['branch0']=branch0
IDT_data['branch1']=branch1
IDT_merged = IDT[0]+IDT[1]
IDT_bounds = shapely_geom.MultiPolygon(IDT_merged)
IDT_bounds = IDT_bounds.convex_hull
IDT_data['convex_hull'] = IDT_bounds
def importSVGroute(IDT_group):
IDT_group_dir = IDT_group['IDT_group_dir']
Tk().withdraw() # we don't want a full GUI, so keep the root window from appearing
svg_filename = tkFileDialog.askopenfilename(title='Wafer routing filename',defaultextension = 'svg',initialdir = IDT_group_dir);
all_points = SVGT.read_colored_path_from_svg(svg_filename)
route =[[],[]]
for points in all_points['red']:
polygon = shapely_geom.Polygon(points.T)
polygon_validity = explain_validity(polygon)
if polygon_validity=='Valid Geometry':
route[0].append(polygon)
else:
tkMessageBox.showwarning('Error in svg import', polygon_validity)
for points in all_points['blue']:
polygon = shapely_geom.Polygon(points.T)
polygon_validity = explain_validity(polygon)
if polygon_validity=='Valid Geometry':
route[1].append(polygon)
else:
tkMessageBox.showwarning('Error in svg import', polygon_validity)
route[0] = shapely_geom.MultiPolygon(route[0])
route[1] = shapely_geom.MultiPolygon(route[1])
#outbox = route[0].bounds
#dx = outbox[2]-outbox[0]
#dy = outbox[3]-outbox[1]
#x0 = outbox[0]+dx/2
#y0 = outbox[1]+dy/2
x0 = 4000
y0 = 4000
factor = 1e-5;
route[0] = shapely_affinity.translate(route[0], xoff=-x0, yoff=-y0)
route[0] = shapely_affinity.scale(route[0], xfact = factor, yfact= factor, origin=(0,0,0))
route[1] = shapely_affinity.translate(route[1], xoff=-x0, yoff=-y0)
route[1] = shapely_affinity.scale(route[1], xfact = factor, yfact= factor, origin=(0,0,0))
IDT_group['route'] = route
test_emptiness.py 文件源码
项目:Vector-Tiles-Reader-QGIS-Plugin
作者: geometalab
项目源码
文件源码
阅读 17
收藏 0
点赞 0
评论 0
def test_empty_multipolygon(self):
self.assertTrue(sgeom.MultiPolygon([]).is_empty)
def load_polygons(im_id: str, im_size: Tuple[int, int])\
-> Dict[int, MultiPolygon]:
return {
int(poly_type): scale_to_mask(im_id, im_size, shapely.wkt.loads(poly))
for poly_type, poly in get_wkt_data()[im_id].items()}
def scale_to_mask(im_id: str, im_size: Tuple[int, int], poly: MultiPolygon)\
-> MultiPolygon:
x_scaler, y_scaler = get_scalers(im_id, im_size)
return shapely.affinity.scale(
poly, xfact=x_scaler, yfact=y_scaler, origin=(0, 0, 0))
def dump_polygons(im_id: str, im_size: Tuple[int, int], polygons: MultiPolygon)\
-> str:
""" Save polygons for submission.
"""
x_scaler, y_scaler = get_scalers(im_id, im_size)
x_scaler = 1 / x_scaler
y_scaler = 1 / y_scaler
polygons = shapely.affinity.scale(
polygons, xfact=x_scaler, yfact=y_scaler, origin=(0, 0, 0))
return shapely.wkt.dumps(polygons)
def mask_for_polygons(
im_size: Tuple[int, int], polygons: MultiPolygon) -> np.ndarray:
""" Return numpy mask for given polygons.
polygons should already be converted to image coordinates.
"""
img_mask = np.zeros(im_size, np.uint8)
if not polygons:
return img_mask
int_coords = lambda x: np.array(x).round().astype(np.int32)
exteriors = [int_coords(poly.exterior.coords) for poly in polygons]
interiors = [int_coords(pi.coords) for poly in polygons
for pi in poly.interiors]
cv2.fillPoly(img_mask, exteriors, 1)
cv2.fillPoly(img_mask, interiors, 0)
return img_mask
def get_polygons(im_id: str, mask: np.ndarray,
epsilon: float, min_area: float, fix: bool, buffer: float
) -> Tuple[MultiPolygon, MultiPolygon]:
assert len(mask.shape) == 2
polygons = utils.mask_to_polygons(
mask, epsilon=epsilon, min_area=min_area, fix=fix)
if buffer:
polygons = utils.to_multipolygon(polygons.buffer(buffer))
x_scaler, y_scaler = utils.get_scalers(im_id, im_size=mask.shape)
x_scaler = 1 / x_scaler
y_scaler = 1 / y_scaler
return polygons, shapely.affinity.scale(
polygons, xfact=x_scaler, yfact=y_scaler, origin=(0, 0, 0))
def consolidate_subdivide_geometry(geometry, max_query_area_size):
"""
Consolidate a geometry into a convex hull, then subdivide it into smaller sub-polygons if its area exceeds max size (in geometry's units).
Parameters
----------
geometry : shapely Polygon or MultiPolygon
the geometry to consolidate and subdivide
max_query_area_size : float
max area for any part of the geometry, in the units the geometry is in.
any polygon bigger will get divided up for multiple queries to API (
default is 50,000 * 50,000 units (ie, 50km x 50km in area, if units are meters))
Returns
-------
geometry : Polygon or MultiPolygon
"""
# let the linear length of the quadrats (with which to subdivide the
# geometry) be the square root of max area size
quadrat_width = math.sqrt(max_query_area_size)
if not isinstance(geometry, (Polygon, MultiPolygon)):
raise ValueError('Geometry must be a shapely Polygon or MultiPolygon')
# if geometry is a MultiPolygon OR a single Polygon whose area exceeds the
# max size, get the convex hull around the geometry
if isinstance(geometry, MultiPolygon) or (isinstance(geometry, Polygon) and geometry.area > max_query_area_size):
geometry = geometry.convex_hull
# if geometry area exceeds max size, subdivide it into smaller sub-polygons
if geometry.area > max_query_area_size:
geometry = quadrat_cut_geometry(geometry, quadrat_width=quadrat_width)
if isinstance(geometry, Polygon):
geometry = MultiPolygon([geometry])
return geometry
def get_polygons_coordinates(geometry):
"""
Extract exterior coordinates from polygon(s) to pass to OSM in a query by
polygon.
Parameters
----------
geometry : shapely Polygon or MultiPolygon
the geometry to extract exterior coordinates from
Returns
-------
polygon_coord_strs : list
"""
# extract the exterior coordinates of the geometry to pass to the API later
polygons_coords = []
if isinstance(geometry, Polygon):
x, y = geometry.exterior.xy
polygons_coords.append(list(zip(x, y)))
elif isinstance(geometry, MultiPolygon):
for polygon in geometry:
x, y = polygon.exterior.xy
polygons_coords.append(list(zip(x, y)))
else:
raise ValueError('Geometry must be a shapely Polygon or MultiPolygon')
# convert the exterior coordinates of the polygon(s) to the string format
# the API expects
polygon_coord_strs = []
for coords in polygons_coords:
s = ''
separator = ' '
for coord in list(coords):
# round floating point lats and longs to 14 places, so we can hash
# and cache strings consistently
s = '{}{}{:.14f}{}{:.14f}'.format(s, separator, coord[1], separator, coord[0])
polygon_coord_strs.append(s.strip(separator))
return polygon_coord_strs
def gen_points(self, dataframe, map):
"""places points from coords in dataset onto a map
Args:
dataframe: dataset to get coords from
map: map
"""
points = pd.Series(
[Point(self.base_map(mapped_x, mapped_y)) for mapped_x, mapped_y in
zip(dataframe['Longitude'], dataframe['Latitude'])])
crimes = MultiPoint(list(points.values))
polygons = prep(MultiPolygon(list(map['poly'].values)))
return list(filter(polygons.contains, crimes))
def project_geometry(geometry, crs, to_latlong=False):
"""
Project a shapely Polygon or MultiPolygon from WGS84 to UTM, or vice-versa
Parameters
----------
geometry : shapely Polygon or MultiPolygon
the geometry to project
crs : int
the starting coordinate reference system of the passed-in geometry
to_latlong : bool
if True, project from crs to WGS84, if False, project
from crs to local UTM zone
Returns
-------
geometry_proj, crs : tuple (projected shapely geometry, crs of the
projected geometry)
"""
gdf = gpd.GeoDataFrame()
gdf.crs = crs
gdf.name = 'geometry to project'
gdf['geometry'] = None
gdf.loc[0, 'geometry'] = geometry
gdf_proj = project_gdf(gdf, to_latlong=to_latlong)
geometry_proj = gdf_proj['geometry'].iloc[0]
return geometry_proj, gdf_proj.crs