def moment_of_inertia(collection): """ Computes the moment of inertia of the polygon. This treats each boundary point as a point-mass of 1. Thus, for constant unit mass at each boundary point, the MoI of this pointcloud is \sum_i d_{i,c}^2 where c is the centroid of the polygon Altman's OS_1 measure, cited in Boyce and Clark (1964), also used in Weaver and Hess (1963). """ ga = _cast(collection) coords = pygeos.get_coordinates(ga) geom_ixs = numpy.repeat(numpy.arange(len(ga)), pygeos.get_num_coordinates(ga)) centroids = pygeos.get_coordinates(pygeos.centroid(ga))[geom_ixs] squared_euclidean = numpy.sum((coords - centroids)**2, axis=1) dists = (pandas.DataFrame.from_dict( dict(d2=squared_euclidean, geom_ix=geom_ixs)).groupby("geom_ix").d2.sum()).values return pygeos.area(ga) / numpy.sqrt(2 * dists)
def test_set_coords(geoms, count, has_ring, include_z): arr_geoms = np.array(geoms, np.object_) n = 3 if include_z else 2 coords = get_coordinates(arr_geoms, include_z=include_z) + np.random.random((1, n)) new_geoms = set_coordinates(arr_geoms, coords) assert_equal(coords, get_coordinates(new_geoms, include_z=include_z))
def test_apply(geoms, include_z): geoms = np.array(geoms, np.object_) coordinates_before = get_coordinates(geoms, include_z=include_z) new_geoms = apply(geoms, lambda x: x + 1, include_z=include_z) assert new_geoms is not geoms coordinates_after = get_coordinates(new_geoms, include_z=include_z) assert_allclose(coordinates_before + 1, coordinates_after, equal_nan=True)
def test_apply(geoms): geoms = np.array(geoms, np.object) coordinates_before = get_coordinates(geoms) new_geoms = apply(geoms, lambda x: x + 1) assert new_geoms is not geoms coordinates_after = get_coordinates(new_geoms) assert_equal(coordinates_before + 1, coordinates_after)
def cut_line_at_points(line, cut_points, tolerance=1e-6): """Cut a pygeos line geometry at points. If there are no interior points, the original line will be returned. Parameters ---------- line : pygeos Linestring cut_points : list-like of pygeos Points will be projected onto the line; those interior to the line will be used to cut the line in to new segments. tolerance : float, optional (default: 1e-6) minimum distance from endpoints to consider the points interior to the line. Returns ------- MultiLineStrings (or LineString, if unchanged) """ if not pg.get_type_id(line) == 1: raise ValueError("line is not a single linestring") vertices = pg.get_point(line, range(pg.get_num_points(line))) offsets = pg.line_locate_point(line, vertices) cut_offsets = pg.line_locate_point(line, cut_points) # only keep those that are interior to the line and ignore those very close # to endpoints or beyond endpoints cut_offsets = cut_offsets[(cut_offsets > tolerance) & (cut_offsets < offsets[-1] - tolerance)] if len(cut_offsets) == 0: # nothing to cut, return original return line # get coordinates of new vertices from the cut points (interpolated onto the line) cut_offsets.sort() # add in the last coordinate of the line cut_offsets = np.append(cut_offsets, offsets[-1]) # TODO: convert this to a pygos ufunc coords = pg.get_coordinates(line) cut_coords = pg.get_coordinates( pg.line_interpolate_point(line, cut_offsets)) lines = [] orig_ix = 0 for cut_ix in range(len(cut_offsets)): offset = cut_offsets[cut_ix] segment = [] if cut_ix > 0: segment = [cut_coords[cut_ix - 1]] while offsets[orig_ix] < offset: segment.append(coords[orig_ix]) orig_ix += 1 segment.append(cut_coords[cut_ix]) lines.append(pg.linestrings(segment)) return pg.multilinestrings(lines)
def test_set_coords(geoms, count, has_ring): geoms = np.array(geoms, np.object) if has_ring: # do not randomize; linearrings / polygons should stay closed coords = get_coordinates(geoms) + np.random.random((1, 2)) else: coords = np.random.random((count, 2)) new_geoms = set_coordinates(geoms, coords) assert_equal(coords, get_coordinates(new_geoms))
def get_ring_coords(polygon): # outer ring must be reversed to be counterclockwise[::-1] coords = [pg.get_coordinates(pg.get_exterior_ring(polygon)).tolist()] for i in range(pg.get_num_interior_rings(polygon)): # inner rings must be reversed to be clockwise[::-1] coords.append( pg.get_coordinates(pg.get_interior_ring(polygon, i)).tolist()) return coords
def perspective_projection(df, sc_coords, sc_heading, fov, width, height, debug=False): lat, lon, alt = sc_coords head, tilt, roll = sc_heading crs_cart = CRS.from_string('+proj=cart') transformer = Transformer.from_crs(df.crs, crs_cart) # cartesian coordinates: # x-axis points from the Earth center to the point of longitude=0, latitude=0 # y-axis points from the Earth center to the point of longitude=90, latitude=0 # z-axis points to the North pole # satellite location in cartesian coords sc_pos_v = np.array(transformer.transform(lat, lon, alt)) # get rotation and projection matrices rot_mx = get_rot_mx(lat, lon, head, tilt, roll) cam_mx = get_cam_mx(fov, width, height) if debug: # test with svalbard coords sval = np.array(transformer.transform(78.148, 16.043, 520)) sc_sv = sval - sc_pos_v sc_sv_cf = rot_mx.dot(sc_sv) * 1e-3 sv_im = cam_mx.dot(sc_sv_cf) print('svalbard at [km] %s, in image [px]: %s' % (sc_sv_cf, sv_im[:2] / sv_im[2])) data = df.geometry.values.data coords = pygeos.get_coordinates(data, include_z=True) # order out: lon, lat shape = coords.shape fc = coords.flatten() fc[np.isnan(fc)] = 0 coords = fc.reshape(shape) loc = transformer.transform(coords[:, 1], coords[:, 0], coords[:, 2]) # order in: lat, lon loc = np.stack(loc, axis=1) rel_loc = loc - sc_pos_v # remove features that are farther away than the horizon r = np.linalg.norm(loc[0, :]) horizon_dist = math.sqrt((r + alt)**2 - r**2) mask = np.linalg.norm(rel_loc, axis=1) > horizon_dist rel_loc[mask, :] = np.nan # rotate and project to image img_coords = cam_mx.dot(rot_mx).dot(rel_loc.T).T img_coords = img_coords[:, :2] / img_coords[:, 2:] new_data = pygeos.set_coordinates(data.copy(), img_coords) new_geom = GeoSeries(GeometryArray(new_data), crs=crs_cart, name=df.geometry.name) df.geometry = new_geom[new_geom.is_valid]
def close_gaps(df, tolerance): """Close gaps in LineString geometry where it should be contiguous. Snaps both lines to a centroid of a gap in between. """ geom = df.geometry.values.data coords = pygeos.get_coordinates(geom) indices = pygeos.get_num_coordinates(geom) # generate a list of start and end coordinates and create point geometries edges = [0] i = 0 for ind in indices: ix = i + ind edges.append(ix - 1) edges.append(ix) i = ix edges = edges[:-1] points = pygeos.points(np.unique(coords[edges], axis=0)) buffered = pygeos.buffer(points, tolerance) dissolved = pygeos.union_all(buffered) exploded = [ pygeos.get_geometry(dissolved, i) for i in range(pygeos.get_num_geometries(dissolved)) ] centroids = pygeos.centroid(exploded) snapped = pygeos.snap(geom, pygeos.union_all(centroids), tolerance) return snapped
def convert_crs(gdf, current_crs="epsg:4326"): """[summary] Args: gdf ([type]): [description] Returns: [type]: [description] """ if current_crs == "epsg:4326": lat = pygeos.geometry.get_y(pygeos.centroid(gdf['geometry'].iloc[0])) lon = pygeos.geometry.get_x(pygeos.centroid(gdf['geometry'].iloc[0])) # formula below based on :https://gis.stackexchange.com/a/190209/80697 approximate_crs = "epsg:" + str( int(32700 - np.round((45 + lat) / 90, 0) * 100 + np.round((183 + lon) / 6, 0))) else: approximate_crs = "epsg:4326" #from pygeos/issues/95 geometries = gdf['geometry'] coords = pygeos.get_coordinates(geometries) transformer = pyproj.Transformer.from_crs(current_crs, approximate_crs, always_xy=True) new_coords = transformer.transform(coords[:, 0], coords[:, 1]) result = pygeos.set_coordinates(geometries.copy(), np.array(new_coords).T) return result, approximate_crs
def _dense_point_array(self, geoms, distance, index): """ geoms - array of pygeos lines """ # interpolate lines to represent them as points for Voronoi points = np.empty((0, 2)) ids = [] if pygeos.get_type_id(geoms[0]) not in [1, 2, 5]: lines = pygeos.boundary(geoms) else: lines = geoms lengths = pygeos.length(lines) for ix, line, length in zip(index, lines, lengths): if length > distance: # some polygons might have collapsed pts = pygeos.line_interpolate_point( line, np.linspace(0.1, length - 0.1, num=int((length - 0.1) // distance)), ) # .1 offset to keep a gap between two segments points = np.append(points, pygeos.get_coordinates(pts), axis=0) ids += [ix] * len(pts) return points, ids
def test_get_coords(geoms, x, y, include_z): actual = get_coordinates(np.array(geoms, np.object_), include_z=include_z) if not include_z: expected = np.array([x, y], np.float64).T else: expected = np.array([x, y, [np.nan] * len(x)], np.float64).T assert_equal(actual, expected)
def test_get_coords_3d(geoms, x, y, z, include_z): actual = get_coordinates(np.array(geoms, np.object_), include_z=include_z) if include_z: expected = np.array([x, y, z], np.float64).T else: expected = np.array([x, y], np.float64).T assert_equal(actual, expected)
def add_distances(network): #Find crs of current gdf and arbitrary point(lat,lon) for new crs current_crs = "epsg:4326" #The commented out crs does not work in all cases #current_crs = [*network.edges.crs.values()] #current_crs = str(current_crs[0]) print(network.nodes.iloc[0]) print(network.nodes) lat = pygeom.get_y(network.nodes['geometry'].iloc[0]) lon = pygeom.get_x(network.nodes['geometry'].iloc[0]) # formula below based on :https://gis.stackexchange.com/a/190209/80697 approximate_crs = "epsg:" + str( int(32700 - np.round((45 + lat) / 90, 0) * 100 + np.round((183 + lon) / 6, 0))) #from pygeos/issues/95 geometries = network.edges['geometry'] coords = pygeos.get_coordinates(geometries) transformer = pyproj.Transformer.from_crs(current_crs, approximate_crs, always_xy=True) new_coords = transformer.transform(coords[:, 0], coords[:, 1]) result = pygeos.set_coordinates(geometries.copy(), np.array(new_coords).T) dist = pygeos.length(result) edges = network.edges.copy() edges['distance'] = dist return Network(nodes=network.nodes, edges=edges)
def export_csv(gdf, path, latlon=False, geom=True, lat_name='lat', lon_name='lot', geom_name='geometry', column_names=None, selection=False, virtual=True, chunksize=1000000, **kwargs): """ Writes GeoDataFrame to a CSV spatial file. """ import pandas as pd sep = kwargs.pop('delimiter', ',') column_names = column_names or gdf.get_column_names(virtual=virtual, strings=True) dtypes = gdf[column_names].dtypes fields = column_names[:] if latlon: fields.append(lat_name) fields.append(lon_name) if geom: fields.append(geom_name) geom_arr = gdf.geometry._geometry if selection not in [None, False] or gdf.filtered: mask = gdf.evaluate_selection_mask(selection) geom_arr = geom_arr.filter(mask) for i1, i2, chunks in gdf.evaluate_iterator(column_names, chunk_size=chunksize, selection=selection): if latlon: coordinates = pg.get_coordinates( pg.centroid(pg.from_wkb(geom_arr[i1:i2]))).T chunks.append(coordinates[0]) chunks.append(coordinates[1]) if geom: chunks.append(pg.to_wkt(pg.from_wkb(geom_arr[i1:i2]))) chunk_dict = {col: values for col, values in zip(fields, chunks)} chunk_pdf = pd.DataFrame(chunk_dict) if i1 == 0: # Only the 1st chunk should have a header and the rest will be appended mode = 'w' header = True else: mode = 'a' header = False chunk_pdf.to_csv(path_or_buf=path, mode=mode, header=header, sep=sep, index=False, **kwargs)
def transform_geometry(self, geom, rs, max_points=5): """Transforms a geometry embedding new points. In case geom is (multi)line or (multi)polygon, it adds points collinear to their neighbours, so that an equivalent geometry is generated. The number of extra points depends on the number of vertices in the geometry. Arguments: geom (pygeos.Geometry): Geometry rs (numpy.RandomState): Random State max_points (int): Maximum value of extra points. Returns: (pygeos.Geometry) Raises: ValueError: When geometry type is not supported. """ type_ = pg.get_type_id(geom) if type_ == 1 or type_ == 3: # LINESTRING or POLYGON vertices = pg.get_coordinates(geom) size = min(max_points, math.ceil(len(vertices) / 6)) vert_ids = rs.randint(1, len(vertices), size) vert_ids.sort() new = [] for idx in vert_ids: xa, ya = vertices[idx - 1] xb, yb = vertices[idx] if xa == xb: x = xa y = self._random_float(rs, ya, yb) else: x = self._random_float(rs, xa, xb) y = (yb - ya) * (x - xa) / (xb - xa) + ya x = _round(x, [xa, xb]) y = _round(y, [ya, yb]) new.append((idx, [x, y])) offset = 0 extended = [] for idx, entry in new: extended.extend(vertices[offset:idx]) extended.append(entry) offset = idx extended.extend(vertices[offset:]) extended = np.array(extended) result = pg.linestrings(extended) if type_ == 1 else pg.polygons( extended) elif type_ == 5 or type_ == 6: # MULTILINESTRING or MULTIPOLYGON parts = pg.get_parts(geom) part_idx = rs.randint(0, len(parts)) parts[part_idx] = self.transform_geometry(parts[part_idx], rs) result = pg.multilinestrings( parts) if type_ == 5 else pg.multipolygons(parts) else: raise ValueError( 'geom should be linestring, polygon, multilinestring, or multipolygon.' ) return result
def get_inverted_coordinates(arr): def invert_list(a): if isinstance(a[0], np.ndarray): return np.array([np.flip(array) for array in a]) else: return np.flip(a) return invert_list(pg.get_coordinates(pg.from_wkb(arr)))
def test_pickle(geom): if pygeos.get_type_id(geom) == 2: # Linearrings get converted to linestrings expected = pygeos.linestrings(pygeos.get_coordinates(geom)) else: expected = geom pickled = pickle.dumps(geom) assert pygeos.equals_exact(pickle.loads(pickled), expected)
def transform(arr, src_crs, tgt_crs): transformer = pyproj.Transformer.from_crs(src_crs, tgt_crs, always_xy=True) geometry = pg.from_wkb(arr) coords = pg.get_coordinates(geometry) new_coords = transformer.transform(coords[:, 0], coords[:, 1]) projected = pg.set_coordinates(geometry, np.array(new_coords).T) return pg.to_wkb(projected)
def transform_geoseries(geometry): target_crs = pyproj.crs.CRS(target) if target_crs == geometry.crs: return geometry cm.check.crs(target_crs) cm.check.crs(geometry.crs) transformer = pyproj.Transformer.from_crs( geometry.crs, target_crs, always_xy=True) if not all(pygeos.has_z(geometry.array.data)): coords = pygeos.get_coordinates( geometry.array.data, include_z=False) new_coords = transformer.transform(coords[:, 0], coords[:, 1]) else: coords = pygeos.get_coordinates( geometry.array.data, include_z=True) new_coords = transformer.transform( coords[:, 0], coords[:, 1], coords[:, 2]) return pygeos.set_coordinates(geometry.array.data.copy(), np.array(new_coords).T)
def convert_point_to_constant_box(input_array, box_size): input_array = np.copy(input_array) for i in range(input_array.shape[0]): x, y = get_coordinates(centroid(input_array[i, 0]))[0] input_array[i, 0] = box(x - (box_size / 2), y - (box_size / 2), x + (box_size / 2), y + (box_size / 2)) return input_array
def test_set_coords_mixed_dimension(include_z): geoms = np.array([point, point_z], dtype=object) coords = get_coordinates(geoms, include_z=include_z) new_geoms = set_coordinates(geoms, coords * 2) if include_z: # preserve original dimensionality assert not pygeos.has_z(new_geoms[0]) assert pygeos.has_z(new_geoms[1]) else: # all 2D assert not pygeos.has_z(new_geoms).any()
def naive_compute_threshold_distance_similarity_matrix(sorted_detections, ground_truths, threshold): """Computes a similarity based on euclidean distance between all pairs of geometries in a naive fashion. Args: sorted_detections (ndarray, list) : A ndarray of detections stored as: * Bounding boxes for a given class where each row is a detection stored as: ``[BoundingBox, confidence]`` * Polygons for a given class where each row is a detection stored as: ``[Polygon, confidence]`` * Points for a given class where each row is a detection stored as: ``[Point, confidence]`` ground_truths (ndarray,list) : A ndarray of ground truth stored as: * Bounding boxes for a given class where each row is a ground truth stored as: ``[BoundingBox]`` * Polygons for a given class where each row is a ground truth stored as: ``[Polygon]`` * Points for a given class where each row is a ground truth stored as: ``[Point]`` Returns: ndarray : An similarity matrix (#detections, #ground truth) """ # We prepare the distance matrix (#detection, #gt) distance_matrix = np.zeros((sorted_detections.shape[0], len(ground_truths))) # Naive iterative distance matrix construction (Note: we iterate over the sorted detections) for k, ground_truth in enumerate(ground_truths): for m, detection in enumerate(sorted_detections): detection_x, detection_y = get_coordinates(centroid(detection[0])).T ground_truth_x, ground_truth_y = get_coordinates(centroid(ground_truth[0])).T if np.absolute(detection_x - ground_truth_x) > threshold \ or np.absolute(detection_y - ground_truth_y) > threshold: distance_matrix[m, k] = np.inf else: distance_matrix[m, k] = distance(centroid(detection[0]), centroid(ground_truth[0])) return 1 - distance_matrix
def close_gaps(gdf, tolerance): """Close gaps in LineString geometry where it should be contiguous. Snaps both lines to a centroid of a gap in between. Parameters ---------- gdf : GeoDataFrame, GeoSeries GeoDataFrame or GeoSeries containing LineString representation of a network. tolerance : float nodes within a tolerance will be snapped together Returns ------- GeoSeries See also -------- momepy.extend_lines momepy.remove_false_nodes """ geom = gdf.geometry.values.data coords = pygeos.get_coordinates(geom) indices = pygeos.get_num_coordinates(geom) # generate a list of start and end coordinates and create point geometries edges = [0] i = 0 for ind in indices: ix = i + ind edges.append(ix - 1) edges.append(ix) i = ix edges = edges[:-1] points = pygeos.points(np.unique(coords[edges], axis=0)) buffered = pygeos.buffer(points, tolerance / 2) dissolved = pygeos.union_all(buffered) exploded = [ pygeos.get_geometry(dissolved, i) for i in range(pygeos.get_num_geometries(dissolved)) ] centroids = pygeos.centroid(exploded) snapped = pygeos.snap(geom, pygeos.union_all(centroids), tolerance) return gpd.GeoSeries(snapped, crs=gdf.crs)
def _extend_line(coords, target, tolerance, snap=True): """ Extends a line geometry to snap on the target within a tolerance. """ if snap: extrapolation = _get_extrapolated_line( coords[-4:] if len(coords.shape) == 1 else coords[-2:].flatten(), tolerance, ) int_idx = target.sindex.query(extrapolation, predicate="intersects") intersection = pygeos.intersection( target.iloc[int_idx].geometry.values.data, extrapolation) if intersection.size > 0: if len(intersection) > 1: distances = {} ix = 0 for p in intersection: distance = pygeos.distance(p, pygeos.points(coords[-1])) distances[ix] = distance ix = ix + 1 minimal = min(distances.items(), key=operator.itemgetter(1))[0] new_point_coords = pygeos.get_coordinates( intersection[minimal]) else: new_point_coords = pygeos.get_coordinates(intersection[0]) coo = np.append(coords, new_point_coords) new = np.reshape(coo, (int(len(coo) / 2), 2)) return new return coords extrapolation = _get_extrapolated_line( coords[-4:] if len(coords.shape) == 1 else coords[-2:].flatten(), tolerance, point=True, ) return np.vstack([coords, extrapolation])
def heatmap(pois, basemap_provider='OpenStreetMap', basemap_name='Mapnik', width='100%', height='100%', radius=10): """Generates a heatmap of the input POIs. Parameters: pois (GeoDataFrame): A POIs GeoDataFrame. basemap_provider (string): The basemap provider. basemap_name: The basemap itself as named by the provider. List and preview of available providers and their basemaps can be found in https://leaflet-extras.github.io/leaflet-providers/preview/ width (integer or percentage): Width of the map in pixels or percentage (default: 100%). height (integer or percentage): Height of the map in pixels or percentage (default: 100%). radius (float): Radius of each point of the heatmap (default: 10). Returns: A Folium Map object displaying the heatmap generated from the POIs. """ # Set the crs to WGS84 if pois.geometry.crs == 'EPSG:4326': pass else: pois.geometry.to_crs('EPSG:4326') # Automatically center the map at the center of the gdf's bounding box bb = pois.geometry.total_bounds() map_center = pg.get_coordinates(pg.centroid(bb))[0].tolist() tiles, attribution, max_zoom = get_provider_info(basemap_provider, basemap_name) heat_map = folium.Map(location=map_center, tiles=tiles, attr=attribution, max_zoom=max_zoom, width=width, height=height) # Automatically set zoom level bounds = pg.total_bounds(bb) heat_map.fit_bounds(([bounds[1], bounds[0]], [bounds[3], bounds[2]])) # List comprehension to make list of lists heat_data = pois.geometry.get_coordinates(invert=True) # Plot it on the map HeatMap(heat_data, radius=radius).add_to(heat_map) return heat_map
def occult(lines: LineCollection, tolerance: float) -> LineCollection: """ Remove occulted lines. The order of the geometries in 'lines' matters, see example below. 'tolerance' controls the distance tolerance between the first and last points of a geometry to consider it closed. Examples: $ vpype line 0 0 5 5 rect 2 2 1 1 occult show # line is occulted by rect $ vpype rect 2 2 1 1 line 0 0 5 5 occult show # line is NOT occulted by rect, as the line is drawn after the rectangle. """ line_arr = np.array( [pygeos.linestrings(list(zip(line.real, line.imag))) for line in lines] ) for i, line in enumerate(line_arr): coords = pygeos.get_coordinates(line) if math.hypot(coords[-1, 0] - coords[0, 0], coords[-1, 1] - coords[0, 1]) < tolerance: tree = pygeos.STRtree(line_arr[:i]) p = pygeos.polygons(coords) geom_idx = tree.query(p, predicate="intersects") line_arr[geom_idx] = pygeos.set_operations.difference(line_arr[geom_idx], p) new_lines = LineCollection() for geom in line_arr: for i in range(pygeos.get_num_geometries(geom)): coords = pygeos.get_coordinates(pygeos.get_geometry(geom, i)) new_lines.append(coords[:, 0] + coords[:, 1] * 1j) return new_lines
def transform(data, func): if compat.USE_PYGEOS: coords = pygeos.get_coordinates(data) new_coords = func(coords[:, 0], coords[:, 1]) result = pygeos.set_coordinates(data.copy(), np.array(new_coords).T) else: from shapely.ops import transform n = len(data) result = np.empty(n, dtype=object) for i in range(n): geom = data[i] result[i] = geom if _isna(geom) else transform(func, geom) return result
def second_areal_moment(collection): """ Using equation listed on en.wikipedia.org/Second_Moment_of_area, the second moment of area is actually the cross-moment of area between the X and Y dimensions: I_xy = (1/24)\sum^{i=N}^{i=1} (x_iy_{i+1} + 2*x_iy_i + 2*x_{i+1}y_{i+1} + x_{i+1}y_i)(x_iy_i - x_{i+1}y_i) where x_i, y_i is the current point and x_{i+1}, y_{i+1} is the next point, and where x_{n+1} = x_1, y_{n+1} = 1. This relation is known as the: - second moment of area - moment of inertia of plane area - area moment of inertia - second area moment and is *not* the mass moment of inertia, a property of the distribution of mass around a shape. """ ga = _cast(collection) result = numpy.zeros(len(ga)) n_holes_per_geom = pygeos.get_num_interior_rings(ga) for i, geometry in enumerate(ga): n_holes = n_holes_per_geom[i] for hole_ix in range(n_holes): hole = pygeos.get_coordinates(pygeos.get_interior_ring( ga, hole_ix)) result[i] -= _second_moa_ring(hole) n_parts = pygeos.get_num_geometries(geometry) for part in pygeos.get_parts(geometry): result[i] += _second_moa_ring(pygeos.get_coordinates(part)) # must divide everything by 24 and flip if polygon is clockwise. signflip = numpy.array([-1, 1])[pygeos.is_ccw(ga).astype(int)] return result * (1 / 24) * signflip
def mbr(self): """Returns the Minimum Bounding Rectangle. Returns: (string) The WKT representation of the MBR. """ if not self._has_geometry: warnings.warn('DataFrame is not spatial.') return None total_bounds = self.df.geometry.total_bounds() transformer = pyproj.Transformer.from_crs(self.crs, "EPSG:4326", always_xy=True) coords = pg.get_coordinates(total_bounds) new_coords = transformer.transform(coords[:, 0], coords[:, 1]) transformed = pg.set_coordinates(total_bounds, np.array(new_coords).T) return pg.to_wkt(transformed)