def test_h3_set_to_multi_polygon_contiguous(): # the second hexagon shares v0 and v1 with the first hexes = ['89283082837ffff', '89283082833ffff'] # multi_polygon mp = h3.h3_set_to_multi_polygon(hexes) vertices0 = h3.h3_to_geo_boundary(hexes[0]) vertices1 = h3.h3_to_geo_boundary(hexes[1]) # We shift the expected circular list so that it starts from # multi_polygon[0][0][0], since output starting from any vertex # would be correct as long as it's in order. expected_coords = shift_circular_list( mp[0][0][0], [ vertices1[0], vertices1[1], vertices1[2], vertices0[1], vertices0[2], vertices0[3], vertices0[4], vertices0[5], vertices1[4], vertices1[5], ] ) expected = [[expected_coords]] assert len(mp) == 1 # polygon count matches expected assert len(mp[0]) == 1 # loop count matches expected assert len(mp[0][0]) == 10 # coord count matches expected assert mp == expected
def test_validation_geo(): h = '8a28308280fffff' # invalid hex with pytest.raises(H3CellError): h3.h3_to_geo(h) with pytest.raises(H3ResolutionError): h3.geo_to_h3(0, 0, 17) with pytest.raises(H3CellError): h3.h3_to_geo_boundary(h) with pytest.raises(H3CellError): h3.h3_indexes_are_neighbors(h, h)
def plotFootprintH3(sat, h3_cells): angle = calcCapAngle(sat.elevation.km, 35) # cells = get_cell_ids_h3(sat.latitude.degrees, sat.longitude.degrees, angle) # print(len(list(cells))) proj = cimgt.Stamen('terrain-background') plt.figure(figsize=(6,6), dpi=400) ax = plt.axes(projection=proj.crs) ax.add_image(proj, 6) # ax.coastlines() ax.set_extent([sat.longitude.degrees-10., sat.longitude.degrees+10., sat.latitude.degrees-10, sat.latitude.degrees+10.], crs=ccrs.Geodetic()) ax.background_patch.set_visible(False) geoms = [] for cellid in h3_cells: # new_cell = s2sphere.Cell(cellid) vertices = [] bounds = h3.h3_to_geo_boundary(cellid) # arrays of [lat, lng] coords = [[lng, lat] for [lat,lng] in bounds] geo = Polygon(coords) geoms.append(geo) ax.add_geometries(geoms, crs=ccrs.Geodetic(), facecolor='red', edgecolor='black', alpha=0.4) ax.plot(sat.longitude.degrees, sat.latitude.degrees, marker='o', color='red', markersize=4, alpha=0.7, transform=ccrs.Geodetic()) plt.savefig('test_h3.png')
def plotFootprintH3(lat: float, lon: float, h3_cells: List): """Uses cartopy to replot the footprint, mostly used for debugging and validating math and library usage """ proj = cimgt.Stamen('terrain-background') plt.figure(figsize=(6, 6), dpi=400) ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180)) ax.add_image(proj, 6) ax.set_extent([lon - 10., lon + 10., lat - 10, lat + 10.], crs=ccrs.Geodetic()) ax.background_patch.set_visible(False) geoms = [] for cellid in h3_cells: vertices = [] bounds = h3.h3_to_geo_boundary(cellid) # arrays of [lat, lng] coords = [[lng, lat] for [lat, lng] in bounds] geo = Polygon(coords) geoms.append(geo) ax.add_geometries(geoms, crs=ccrs.Geodetic(), facecolor='red', edgecolor='black', alpha=0.4) ax.plot(lon, lat, marker='o', color='red', markersize=4, alpha=0.7, transform=ccrs.Geodetic()) plt.savefig('test_h3.png')
def counts_by_hexagon(plot_variable, df, resolution): """ Use h3.geo_to_h3 to index each data point into the spatial index of the specified resolution. Use h3.h3_to_geo_boundary to obtain the geometries of these hexagons Ex counts_by_hexagon(data, 9) """ df = df[["lat", "lng", plot_variable]] df["hex_id"] = df.apply( lambda row: h3.geo_to_h3(row["lat"], row["lng"], resolution), axis=1) df_aggreg = df.groupby(by="hex_id").size().reset_index() # print(df_aggreg.columns) # import numpy as np df_aggreg = df.groupby(['hex_id'], as_index=True).agg({ plot_variable: 'mean' }).rename(columns={ plot_variable: 'value' }).reset_index() df_aggreg = df_aggreg[['hex_id', 'value']] df_aggreg = df_aggreg[df_aggreg['value'] != np.inf] df_aggreg["geometry"] = df_aggreg.hex_id.apply( lambda x: { "type": "Polygon", "coordinates": [h3.h3_to_geo_boundary(h=x, geo_json=True)] }) return df_aggreg
def h3_to_geo_boundary_geojson(): import h3 # 1. convertit un tuples de tuples en tableau 3d (tableau de tableau de # tableau) coordinates_geojson = [ list( map( lambda h: list(h), h3.h3_to_geo_boundary(h="881f89b0b9fffff", geo_json=True), )) ] # 2. formatte en geojson hexagon_geojson = { "type": "Polygon", "coordinates": coordinates_geojson, } # 3. afficht les resulats print(hexagon_geojson) print("") # returns { "type": "Polygon", "coordinates": [[ [12.99819043295637, 48.00281717023803], [12.996138504924922, 47.998312559890245], [13.001125772015417, 47.99513376112209], [13.008165144025583, 47.996459408079176], [13.010217832868259, 48.00096400408189], [13.005230388946893, 48.00414296749273], [12.99819043295637, 48.00281717023803], ]], }
def test_h3_set_to_multi_polygon_single(): h = '89283082837ffff' hexes = {h} # multi_polygon mp = h3.h3_set_to_multi_polygon(hexes) vertices = h3.h3_to_geo_boundary(h) # We shift the expected circular list so that it starts from # multi_polygon[0][0][0], since output starting from any vertex # would be correct as long as it's in order. expected_coords = shift_circular_list( mp[0][0][0], [ vertices[2], vertices[3], vertices[4], vertices[5], vertices[0], vertices[1], ] ) expected = [[expected_coords]] assert mp == expected
def h3_2set_polyfill(geometry, resolution): def to_h3(geometry): # buffering can result in MultiPolygon geometries if 'MultiPolygon' in geometry.geom_type: geometry = list(geometry.geoms) else: geometry = [geometry] hex_set = set() for p in geometry: p_geojson = shapely.geometry.mapping(p) hex_set = hex_set.union(h3.polyfill_geojson(p_geojson, resolution)) return hex_set def get_result_struct(hex_i, polygon, dirty_set): if hex_i in dirty_set: hex_polygon = shapely.geometry.Polygon( h3.h3_to_geo_boundary(hex_i, geo_json=True)) intersection = polygon.intersection(hex_polygon) if intersection.is_empty: # does not represent any area of the original geometry return None elif intersection.equals( hex_polygon): # fully contained by the original geometry return (hex_i, False, None) else: return (hex_i, True, intersection.wkb ) # partially contained by the original geometry else: return ( hex_i, False, None ) # fully contained by the original geometry (not in the dirty set) polygon = geometry # placeholder for when we are loading wkt/wkb in udfs # get centroid of the geometry # get centroid index # get centroid index geometry # compute radius based on teh minimum enclosing rectangel (radius of pseudo minimal enclosing circle) cent = polygon.centroid.xy centroid_hex = h3.geo_to_h3(cent[1][0], cent[0][0], resolution) centroid_geom = shapely.geometry.Polygon( h3.h3_to_geo_boundary(centroid_hex, geo_json=True)) radius = math.sqrt(centroid_geom.minimum_rotated_rectangle.area) / 2 dirty = polygon.boundary.buffer(radius) original_set = to_h3(polygon) dirty_set = to_h3(dirty) result = [ get_result_struct(hex_i, polygon, dirty_set) for hex_i in list(original_set.union(dirty_set)) ] result = [c for c in result if c is not None] return result
def ids_to_geojson(h3_ids): features = [] for hex in h3_ids: my_feature = Feature( geometry=Polygon([h3.h3_to_geo_boundary(h=hex, geo_json=True)])) features.append(my_feature) feature_collection = FeatureCollection(features) return feature_collection
def h3_2set_polyfill(geometry, resolution): # we need to account for possibility of MultiPolygon shapes def to_h3(geometry): if 'MultiPolygon' in geometry.geom_type: geometry = list(geometry.geoms) else: geometry = [geometry] hex_set = set() for p in geometry: p_geojson = shapely.geometry.mapping(p) hex_set = hex_set.union(h3.polyfill_geojson(p_geojson, resolution)) return hex_set # convenience method for converting an index to a chip of a polygon def get_result_struct(hex_i, polygon, dirty_set): if hex_i in dirty_set: hex_polygon = shapely.geometry.Polygon( h3.h3_to_geo_boundary(hex_i, geo_json=True)) intersection = polygon.intersection(hex_polygon) if intersection.is_empty: return None elif intersection.equals(hex_polygon): return (hex_i, False, None) else: return (hex_i, True, intersection.wkb) else: return (hex_i, False, None) polygon = shapely.wkb.loads(bytes(geometry)) # compute the buffer radius # we cannot use the hexagon side lenght due to curvatrure # we use minimum rotated rectange - we assume that the rectangle is near rectangle in shape # the alternative would be to iterate through boundary vertices and take the longest side cent = polygon.centroid.xy centroid_hex = h3.geo_to_h3(cent[1][0], cent[0][0], resolution) centroid_geom = shapely.geometry.Polygon( h3.h3_to_geo_boundary(centroid_hex, geo_json=True)) radius = math.sqrt(centroid_geom.minimum_rotated_rectangle.area) / 2 # any index that may touch the boundary dirty = polygon.boundary.buffer(radius) original_set = to_h3(polygon) dirty_set = to_h3(dirty) result = [ get_result_struct(hex_i, polygon, dirty_set) for hex_i in list(original_set.union(dirty_set)) ] return result
def test4(): expected = ((-122.41719971841658, 37.775197782893386), (-122.41612835779264, 37.77688044840226), (-122.4173879761762, 37.778385004930925), (-122.41971895414807, 37.77820687262238), (-122.42079024541877, 37.77652420699321), (-122.4195306280734, 37.775019673792606), (-122.41719971841658, 37.775197782893386)) out = h3.h3_to_geo_boundary('8928308280fffff', geo_json=True) assert approx2(out, expected)
def get_path_from_h3(h3id, color="0xFF0000AA", weight=1, fillcolor="0xFFB6C1BB"): boundry = h3.h3_to_geo_boundary(h3id) poly_lines = list(boundry) poly_lines.append(boundry[0]) polyline_code = convert.encode_polyline(poly_lines) return MAP_PATH.format( color=color, weight=weight, fcolor=fillcolor, poly_code=polyline_code )
def test3(): expected = ( (37.775197782893386, -122.41719971841658), (37.77688044840226, -122.41612835779264), (37.778385004930925, -122.4173879761762), (37.77820687262238, -122.41971895414807), (37.77652420699321, -122.42079024541877), (37.775019673792606, -122.4195306280734), ) out = h3.h3_to_geo_boundary('8928308280fffff') assert approx2(out, expected)
def cell_perimiter2(h, unit='km'): verts = h3.h3_to_geo_boundary(h) N = len(verts) verts += (verts[0], ) dists = [ h3.point_dist(verts[i], verts[i + 1], unit=unit) for i in range(N) ] assert all(d > 0 for d in dists) return sum(dists)
def get_result_struct(hex_i, polygon, dirty_set): if hex_i in dirty_set: hex_polygon = shapely.geometry.Polygon( h3.h3_to_geo_boundary(hex_i, geo_json=True)) intersection = polygon.intersection(hex_polygon) if intersection.is_empty: return None elif intersection.equals(hex_polygon): return (hex_i, False, None) else: return (hex_i, True, intersection.wkb) else: return (hex_i, False, None)
def h3_to_geo_boundary(): import h3 hexagon_tuple2d = h3.h3_to_geo_boundary(h="881f89b0b9fffff") print(hexagon_tuple2d) # returns ( (48.00281717023803, 12.99819043295637), (47.998312559890245, 12.996138504924922), (47.99513376112209, 13.001125772015417), (47.996459408079176, 13.008165144025583), (48.00096400408189, 13.010217832868259), (48.00414296749273, 13.005230388946893), )
def _to_hex(source, resolution=6, return_geoms=True): """Generate a hexgrid geodataframe that covers the face of a source geometry. Parameters ---------- source : geometry geometry to transform into a hexagonal grid (needs to support __geo_interface__) resolution : int, optional (default is 6) resolution of output h3 hexgrid. See <https://h3geo.org/docs/core-library/restable> for more information return_geoms: bool, optional (default is True) whether to generate hexagon geometries as a geodataframe or simply return hex ids as a pandas.Series Returns ------- pandas.Series or geopandas.GeoDataFrame if `return_geoms` is True, a geopandas.GeoDataFrame whose rows comprise a hexagonal h3 grid (indexed on h3 hex id). if `return_geoms` is False, a pandas.Series of h3 hexagon ids """ try: import h3 except ImportError: raise ImportError( "This function requires the `h3` library. " "You can install it with `conda install h3-py` or " "`pip install h3`" ) hexids = pandas.Series( list( h3.polyfill( source.__geo_interface__, resolution, geo_json_conformant=True, ) ), name="hex_id", ) if not return_geoms: return hexids polys = hexids.apply( lambda hex_id: Polygon(h3.h3_to_geo_boundary(hex_id, geo_json=True)), ) hexs = geopandas.GeoDataFrame(hexids, geometry=polys, crs=4326).set_index("hex_id") return hexs
def h3_polyfill_extended(geometry, resolution): (cx, cy) = polygon.centroid.coords.xy # get centroid location centroid_ind = h3.geo_to_h3( cx[0], cy[0], resolution) # get h3 index containing the centroid centroid_geom = shapely.geometry.Polygon( h3.h3_to_geo_boundary(centroid_ind)) # get centroid index geometry radius = math.sqrt( centroid_geom.minimum_rotated_rectangle.area ) / 2 # find the radius of (pseudo) minimal enclosing circle (via side of the rotated enclosing square) geom_extended = geometry.buffer( distance=radius, resolution=1 ) # buffer the original geometry by the radius of minimum enclosing cicle geo_json_geom = shapely.geometry.mapping(geom_extended) indices = h3.polyfill_geojson(geo_json_geom, resolution) # get the indices return indices
def get_random_inner_point(hex_key: str) -> Point: """ function to get a random point inside a hexagon Args: hex_key (str): hex key value Returns: Point: shapely.geometry.Point with the (x,y) pair on the hexagon """ hex_polygon = Polygon(h3.h3_to_geo_boundary(hex_key)) minx, miny, maxx, maxy = hex_polygon.bounds while True: random_point = Point(random.uniform(minx, maxx), random.uniform(miny, maxy)) if hex_polygon.contains(random_point): return random_point
def test_h3_to_geo_boundary(): out = h3.h3_to_geo_boundary('85283473fffffff') expected = [ [37.271355866731895, -121.91508032705622], [37.353926450852256, -121.86222328902491], [37.42834118609435, -121.9235499963016], [37.42012867767778, -122.0377349642703], [37.33755608435298, -122.09042892904395], [37.26319797461824, -122.02910130919], ] assert len(out) == len(expected) for o, e in zip(out, expected): assert o == approx(e)
def get_result_struct(hex_i, polygon, dirty_set): if hex_i in dirty_set: hex_polygon = shapely.geometry.Polygon( h3.h3_to_geo_boundary(hex_i, geo_json=True)) intersection = polygon.intersection(hex_polygon) if intersection.is_empty: # does not represent any area of the original geometry return None elif intersection.equals( hex_polygon): # fully contained by the original geometry return (hex_i, False, None) else: return (hex_i, True, intersection.wkb ) # partially contained by the original geometry else: return ( hex_i, False, None ) # fully contained by the original geometry (not in the dirty set)
def extract_features(year, month, radius, aperture_size): incident_df = load_incidents(aperture_size) grid_cell_hex_index = np.unique(incident_df.loc[:, 'region']) grid_cell_hex_bound = [ h3.h3_to_geo_boundary(h) for h in grid_cell_hex_index ] grid_cells_hex = [ Polygon([(x, y) for (y, x) in h]) for h in grid_cell_hex_bound ] grid_cell_index_by_id = dict( (id(c), i) for i, c in zip(grid_cell_hex_index, grid_cells_hex)) grid_cell_index = STRtree(grid_cells_hex) gdf = pd.read_pickle(f'output/waze/{year}_{month}_{radius}_region.pkl') gdf['time'] = pd.to_datetime(gdf['pubMillis'], unit='ms') waze_report_uncertainty_regions = gdf.loc[:, 'region'] likelihood = [] for waze_report_region in tqdm(waze_report_uncertainty_regions): _probs = [] _total_overlap_area = 0.0 for grid_cell in grid_cell_index.query(waze_report_region): region_intersect = grid_cell.intersection(waze_report_region) _id = grid_cell_index_by_id[id(grid_cell)] _overlap_area = region_intersect.area _total_overlap_area += _overlap_area if _overlap_area > 0.0: _probs.append((_id, _overlap_area)) _probs = [(i, p / _total_overlap_area) for i, p in _probs] likelihood.append(_probs) records = [] # Consider an accident occurring at a given time if values are in between this period items = list(zip(gdf.loc[:, 'time'], gdf.loc[:, 'reliability'], likelihood)) for time, reliability, region_likelihood in tqdm(items): record = {'time': time, 'reliability': reliability} record.update(dict({f'r_{r}': l for r, l in region_likelihood})) records.append(record) # df_temp = incident_df[(incident_df.time >= (time - post_dt)) & (incident_df.time <= (time + prev_dt))] records = pd.DataFrame(records).fillna(0) records.to_pickle( f'output/waze/{year}_{month}_{radius}_{aperture_size}_features.pkl')
def test_h3_set_to_multi_polygon_single_geo_json(): hexes = ['89283082837ffff'] mp = h3.h3_set_to_multi_polygon(hexes, True) vertices = h3.h3_to_geo_boundary(hexes[0], True) # We shift the expected circular list so that it starts from # multi_polygon[0][0][0], since output starting from any vertex # would be correct as long as it's in order. expected_coords = shift_circular_list( mp[0][0][0], [ vertices[2], vertices[3], vertices[4], vertices[5], vertices[0], vertices[1] ] ) expected = [[expected_coords]] # polygon count matches expected assert len(mp) == 1 # loop count matches expected assert len(mp[0]) == 1 # coord count 7 matches expected according to geojson format assert len(mp[0][0]) == 7 # first coord should be the same as last coord according to geojson format assert mp[0] == mp[-1] # the coord should be (lng, lat) according to geojson format assert mp[0][0][0][0] == approx(-122.42778275313199) assert mp[0][0][0][1] == approx(37.77598951883773) # Discard last coord for testing below, since last coord is # the same as the first one mp[0][0].pop() assert mp == expected
def sum_by_hexagon(df, resolution, pol, fr, to, vessel_type=[], gt=[]): """ Use h3.geo_to_h3 to index each data point into the spatial index of the specified resolution. Use h3.h3_to_geo_boundary to obtain the geometries of these hexagons Ex counts_by_hexagon(data, 8) """ if vessel_type: df_aggreg = df[((df.dt_pos_utc.between(fr, to)) & (df.StandardVesselType.isin(vessel_type)))] else: df_aggreg = df[df.dt_pos_utc.between(fr, to)] if df_aggreg.shape[0] > 0: if gt: df_aggreg = df_aggreg[df_aggreg.GrossTonnage.between(gt[0], gt[1])] if resolution == 8: df_aggreg = df_aggreg.groupby(by="res_8").agg({ "co2_t": sum, "ch4_t": sum }).reset_index() else: df_aggreg = df_aggreg.assign(new_res=df_aggreg.res_8.apply( lambda x: h3.h3_to_parent(x, resolution))) df_aggreg = df_aggreg.groupby(by="new_res").agg({ "co2_t": sum, "ch4_t": sum }).reset_index() df_aggreg.columns = ["hex_id", "co2_t", "ch4_t"] df_aggreg["geometry"] = df_aggreg.hex_id.apply( lambda x: { "type": "Polygon", "coordinates": [h3.h3_to_geo_boundary(x, geo_json=True)] }) return df_aggreg else: return df_aggreg
def h3_tile_by_dim(lon, lat, bdim=BASE_DIM, asc=True, more=False): osmproj = pyproj.Proj("epsg:3857") resolutions = range(H3_MAX_ZOOM) if asc else reversed(range(H3_MAX_ZOOM)) for resolution in resolutions: tile = h3.geo_to_h3(lat, lon, resolution) polygon = h3.h3_to_geo_boundary(tile) x1, y1 = osmproj(*polygon[0]) x2, y2 = osmproj(*polygon[1]) dist = Point(x1, y1).distance(Point(x2, y2)) if (asc and dist < bdim) or (not asc and dist > bdim): break return tile if not more else ( tile, resolution, )
def plot_exposure(exposure, date): # Cette fonction est à bidouiller à la main pour chaque matrice exposure events = pd.DataFrame(exposure.loc[:, date].copy()) events['geometry'] = events.apply( lambda row: Polygon(h3.h3_to_geo_boundary(row.name, True)), axis=1) events.columns = ['exposure', 'geometry'] events = gpd.GeoDataFrame(events, crs='EPSG:4326') vmin, vmax = 0, exposure.max().max() _, ax = plt.subplots(1, 1) events.plot(column='exposure', ax=ax, cmap='OrRd', figsize=(10, 10), vmin=vmin, vmax=vmax, legend=True, norm=plt.Normalize(vmin=vmin, vmax=vmax)) plt.autoscale(False) world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres')) world.to_crs(events.crs).plot(ax=ax, color='none', edgecolor='black') ax.axis('off') ax.set_title(f'Conventional warfare in Syria in {date.year}') # C'est une horrible manière de faire, mais je n'ai pas le temps de réfléchir months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] ax.annotate(months[date.month - 1], xy=(0.5, .3), xycoords='figure fraction', horizontalalignment='left', verticalalignment='top', fontsize=16)
def fill_hexagons(geom_geojson, res, flag_swap=False, flag_return_df=False): """Fill the input polygons with h3 hexagons. The result of each polygon is stored in a single dataframe item. Note: Each time you execute this function, the set of hexagons is the same, but the sequence order is different.""" set_hexagons = h3.polyfill(geojson=geom_geojson, res=res, geo_json_conformant=flag_swap) list_hexagons_filling = list(set_hexagons) if flag_return_df is True: df_fill_hex = pd.DataFrame({"hex_id": list_hexagons_filling}) df_fill_hex["geometry"] = df_fill_hex.hex_id.apply( lambda x: {"type": "Polygon", "coordinates": [ h3.h3_to_geo_boundary(h=x, geo_json=True) ] }) assert (df_fill_hex.shape[0] == len(list_hexagons_filling)) return df_fill_hex else: return list_hexagons_filling
def prepare(df): """Prepare the data to make them plottable This includes * generating the polygons to every point * Merging overlapping points * Removing unplottable polygons. :param df: Data to prepare :type df: geopandas.GeoDataFrame :return: Prepared data :rtype: geopandas.GeoDataFrame """ logger.info("Preparing the data") logger.debug("Generating h3 indices") df["hex"] = [ h3.geo_to_h3(lat, lon, RESOLUTION) for lat, lon in zip(df.geometry.y, df.geometry.x) ] logger.debug("Merging overlapping points") df = df.groupby("hex", as_index=False).agg({"value": "mean"}) logger.debug("Calculating hexagonal polygons") df["geometry"] = geopandas.GeoSeries([ Polygon(revert(polygon)) for polygon in [h3.h3_to_geo_boundary(h) for h in df.hex] ]) df = geopandas.GeoDataFrame(df, geometry=df.geometry) logger.debug("Calculating bounds of the polygons") bounds = df.bounds logger.debug("Removing polygons crossing the map border") df = df.mask(bounds.maxy - bounds.miny > 180) df = df.mask(bounds.maxx - bounds.minx > 180) return df
def add_geometry(row): points = h3.h3_to_geo_boundary(row['h3'], True) return Polygon(points)
colormap=colormap, marker=marker, alpha=alpha, figsize=figsize) plt.xticks([], []) plt.yticks([], []) # pltot the hexs plot_scatter(incident_g_df, metric_col='cnt', marker='o', figsize=(16, 12)) plt.title('hex-grid: accidents (incident)') st.pyplot() grid_cells = [ MultiPoint(h3.h3_to_geo_boundary(h)).bounds for h in np.unique(incident_df[hex_col]) ] for pos, cell_bounds in enumerate(grid_cells): idx.insert(pos, cell_bounds) radius = 500 # in meters # Obtain polygons for each WAZE report polygons = [] progress_bar = st.progress(0) progress_bar_len = report_df.shape[0] progress_bar_count = 0.0 for lat, lng in zip(report_df.lat, report_df.lng): local_azimuthal_projection = "+proj=aeqd +R=6371000 +units=m +lat_0={} +lon_0={}".format(