streamlit 应用程序中城市的每个 H3 六边形的 POI 数量 - 六边形未显示

问题描述 投票:0回答:1

我创建了一个streamlit应用程序,它应该让用户选择一个区域,然后计算该区域中每个H3六边形单元的POI数量。不知何故,六边形已创建但未显示在地图上。这是代码:

import folium
from streamlit_folium import st_folium
import requests
import h3

# Set layout
st.set_page_config(layout="wide", page_title="POIs per Hexagon")

# Define colors for each amenity (example)
amenity_colors = {
    "restaurant": "blue",
    "bank": "green",
    "post_box": "red",
    "atm": "purple",
    "post_office": "orange",
    "marketplace": "pink",
    "pharmacy": "brown",
    "hospital": "gray",
    "clinic": "yellow",
    "doctor": "cyan",
    "dentist": "olive",
    "kindergarten": "lime",
    "childcare": "teal",
    "school": "magenta",
    "fast_food": "navy",
    "cafe": "maroon",
    "bar": "gold",
    "pub": "indigo",
    "ice_cream": "tan",
    "nightclub": "coral",
    "biergarten": "lavender",
    "community_centre": "slateblue",
    "library": "orchid",
    "theatre": "skyblue",
    "cinema": "seagreen",
    "arts_centre": "khaki",
    "events_venue": "darkred"
}

# Main Content
st.title("Calculate the POIs per Hexagon")
st.write("blablabla explaining text")

st.header("Choose a City")
st.write("Choose an area on the map below. The visible area of the map will be used to create the count.")

lat, lon = 49.40768, 8.69079  # Default coordinates

# Initialize session state for map markers and bounds
if "map_initialized" not in st.session_state:
    st.session_state.map_initialized = False
if "map_bounds" not in st.session_state:
    st.session_state.map_bounds = None
if "map_markers" not in st.session_state:
    st.session_state.map_markers = []


def get_center_from_bounds(bounds):
    """Calculate the center of the map from its bounds."""
    center_lat = (bounds['_southWest']['lat'] + bounds['_northEast']['lat']) / 2
    center_lon = (bounds['_southWest']['lng'] + bounds['_northEast']['lng']) / 2
    return center_lat, center_lon


def fetch_pois(sw_lat, sw_lng, ne_lat, ne_lng):
    """Fetch POIs (Points of Interest) from OSM using the Overpass API."""
    overpass_url = "http://overpass-api.de/api/interpreter"
    amenities = [
        "restaurant", "bank", "post_box", "atm", "post_office", "marketplace",
        "pharmacy", "hospital", "clinic", "doctor", "dentist", "kindergarten",
        "childcare", "school", "fast_food", "cafe", "bar", "pub", "ice_cream",
        "nightclub", "biergarten", "community_centre", "library", "theatre",
        "cinema", "arts_centre", "events_venue"
    ]
    
    nodes = "\n".join(
        f'node["amenity"="{amenity}"]({sw_lat},{sw_lng},{ne_lat},{ne_lng});' 
        for amenity in amenities
    )
    
    overpass_query = f"""
    [out:json];
    (
    {nodes}
    );
    out body;
    """
    
    response = requests.get(overpass_url, params={"data": overpass_query})
    return response


def add_markers_to_map(m, markers):
    """Add markers to the map with custom colors based on amenity type."""
    for marker in markers:
        amenity_type = marker['tooltip'].lower()  # assuming tooltip contains amenity type
        color = amenity_colors.get(amenity_type, 'gray')  # default to gray if type not found
        folium.Marker(location=marker['location'], popup=marker['popup'], tooltip=marker['tooltip'],
                      icon=folium.Icon(color=color)).add_to(m)


def display_map_with_bounds(lat, lon, zoom_start=12):
    """Display the map with given latitude, longitude, and zoom level."""
    m = folium.Map(location=[lat, lon], zoom_start=zoom_start)
    bounds_script = """
    <script>
    function updateBounds() {
        const bounds = map.getBounds();
        const data = {
            _northEast: bounds.getNorthEast(),
            _southWest: bounds.getSouthWest()
        };
        Streamlit.setComponentValue(data);
    }
    map.on('moveend', updateBounds);
    updateBounds();
    </script>
    """
    m.get_root().html.add_child(folium.Element(bounds_script))
    add_markers_to_map(m, st.session_state.map_markers)

    # Add legend
    legend_html = """
    <div style="position: fixed; bottom: 50px; left: 50px; z-index:1000; font-size:14px; background:white; padding: 10px; border: 2px solid black;">
      <p><strong>Legend</strong></p>
      """
    for amenity, color in amenity_colors.items():
        legend_html += f'<p><i class="fa fa-circle" style="color:{color}"></i> {amenity}</p>'
    legend_html += "</div>"

    m.get_root().html.add_child(folium.Element(legend_html))

    return st_folium(m, width=700, height=500)


def create_hexagons(pois, resolution):
    hex_counts = {}
    for poi in pois:
        hex_addr = h3.geo_to_h3(poi['location'][0], poi['location'][1], resolution)
        if hex_addr in hex_counts:
            hex_counts[hex_addr] += 1
        else:
            hex_counts[hex_addr] = 1
    return hex_counts


def add_hexagons_to_map(m, hex_counts, resolution):
    for hex_addr, count in hex_counts.items():
        hex_boundary = h3.h3_to_geo_boundary(hex_addr, geo_json=True)
        folium.Polygon(locations=hex_boundary, color='black', fill=True, fill_opacity=0.9,
                       tooltip=f'POIs: {count}').add_to(m)


# Display initial map or map with markers
if st.session_state.map_bounds:
    center_lat, center_lon = get_center_from_bounds(st.session_state.map_bounds)
    output = display_map_with_bounds(center_lat, center_lon)
else:
    output = display_map_with_bounds(lat, lon)

# Handle button click for "Create Score"
if st.button("Create Score"):
    if 'bounds' in output:
        bounds = output['bounds']
        st.session_state.map_bounds = bounds  # Save bounds in session state

        # create variables for coordinates
        if '_southWest' in bounds and '_northEast' in bounds:
            sw_lat = bounds['_southWest']['lat']
            sw_lng = bounds['_southWest']['lng']
            ne_lat = bounds['_northEast']['lat']
            ne_lng = bounds['_northEast']['lng']

            # fetch pois and store to variable
            response = fetch_pois(sw_lat, sw_lng, ne_lat, ne_lng)

            # if response is correct, then show in map as markers
            if response.status_code == 200:
                try:
                    data = response.json()
                    st.session_state.map_markers.clear()
                    pois = []
                    for element in data['elements']:
                        if element['type'] == 'node':
                            marker = {
                                'location': [element['lat'], element['lon']],
                                'popup': element.get('tags', {}).get('name', 'Unnamed'),
                                'tooltip': element.get('tags', {}).get('amenity', 'Restaurant')
                            }
                            pois.append(marker)
                            st.session_state.map_markers.append(marker)

                    # create hexagons
                    resolution = 8  # Adjust resolution as needed
                    hex_counts = create_hexagons(pois, resolution)

                    # Debug: Output hex_counts to verify
                    st.write(f"Hex Counts: {hex_counts}")

                    # get center coordinates of bbox
                    center_lat, center_lon = get_center_from_bounds(st.session_state.map_bounds)
                    # display map according to bbox
                    m = folium.Map(location=[center_lat, center_lon], zoom_start=12)
                    add_markers_to_map(m, st.session_state.map_markers)
                    add_hexagons_to_map(m, hex_counts, resolution)
                    st_folium(m, width=700, height=500)
                except ValueError:
                    st.error("Error decoding JSON response from Overpass API")
                    st.text(response.text)
            else:
                st.error(f"Error fetching data from Overpass API: {response.status_code}")
                st.text(response.text)
        else:
            st.write("Bounding box keys are missing.")
    else:
        st.write("No bounding box information available.")

在生成过程中,我可以看到六边形存在几秒钟:st.write(f"Hex Counts: {hex_counts}")。但它们没有画在地图上。这是结果:

enter image description here

感谢您的任何提示!

python openstreetmap streamlit folium h3
1个回答
1
投票

您的代码中有很多问题,但主要的问题(看不见的六边形)是由于某种原因

h3.h3_to_geo_boundary
返回
lon/lat
坐标而不是
lat/lon
folium期望) ):

def add_hexagons_to_map(hex_counts, resolution):
    for hex_addr, count in hex_counts.items():
        hex_boundary = h3.h3_to_geo_boundary(hex_addr, geo_json=True)
        poly = folium.Polygon(
            locations=[lonlat[::-1] for lonlat in hex_boundary], # << flip the lonlat
            color="black",
            fill=True,
            fill_opacity=0.3,
            tooltip=f"POIs: {count}",
        )

输出(

streamlit run .\app.py
):

© www.soinside.com 2019 - 2024. All rights reserved.