Skip to content

Commit

Permalink
Coverage map.
Browse files Browse the repository at this point in the history
  • Loading branch information
iwatkot committed Feb 17, 2025
1 parent 9f9a3cc commit 909ecfb
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 35 deletions.
31 changes: 25 additions & 6 deletions maps4fs/generator/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@
logger.debug("API_TOKEN not set in environment")


def send_settings(endpoint: str, data: dict[str, Any]) -> None:
"""Send settings to the statistics server.
def post(endpoint: str, data: dict[str, Any]) -> dict[str, Any] | None:
"""Make a POST request to the statistics server.
Arguments:
endpoint (str): The endpoint to send the settings to.
data (dict[str, Any]): The settings to send.
endpoint (str): The endpoint to send the request to.
data (dict[str, Any]): The data to send.
Returns:
dict[str, Any]: The response from the server.
"""
if not STATS_HOST or not API_TOKEN:
logger.info("STATS_HOST or API_TOKEN not set in environment, can't send settings.")
Expand All @@ -41,6 +44,7 @@ def send_settings(endpoint: str, data: dict[str, Any]) -> None:
logger.error("Failed to send settings: %s", response.text)
else:
logger.info("Settings sent successfully")
return response.json()


def send_main_settings(data: dict[str, Any]) -> None:
Expand All @@ -50,7 +54,7 @@ def send_main_settings(data: dict[str, Any]) -> None:
data (dict[str, Any]): The main settings to send.
"""
endpoint = f"{STATS_HOST}/receive_main_settings"
send_settings(endpoint, data)
post(endpoint, data)


def send_advanced_settings(data: dict[str, Any]) -> None:
Expand All @@ -60,4 +64,19 @@ def send_advanced_settings(data: dict[str, Any]) -> None:
data (dict[str, Any]): The advanced settings to send.
"""
endpoint = f"{STATS_HOST}/receive_advanced_settings"
send_settings(endpoint, data)
post(endpoint, data)


def get_main_settings(fields: list[str], limit: int | None = None) -> list[dict[str, Any]] | None:
"""Get main settings from the statistics server.
Arguments:
fields (list[str]): The fields to get.
limit (int | None): The maximum number of settings to get.
Returns:
list[dict[str, Any]]: The settings from the server.
"""
endpoint = f"{STATS_HOST}/get_main_settings"
data = {"fields": fields, "limit": limit}
return post(endpoint, data).get("settings")
144 changes: 115 additions & 29 deletions webui/osmp.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,140 @@
import math
import random
from typing import NamedTuple

import folium
import osmnx as ox


def get_rotated_preview(lat: float, lon: float, distance: int, angle: int) -> folium.Map:
class MapEntry(NamedTuple):
"""Represents a map entry."""

latitude: float
longitude: float
size: int
rotation: int


def get_rotated_previews(
entries: list[MapEntry],
add_markers: bool = False,
add_bboxes: bool = True,
) -> folium.Map:
"""Return the path to the HTML file where the OpenStreetMap data is saved.
Arguments:
entries (list[MapEntry]): List of map entries.
add_markers (bool): True if the center markers should be added, False otherwise.
add_bboxes (bool): True if the bounding boxes should be added, False otherwise.
Returns:
folium.Map: Folium map object.
"""
m = folium.Map(zoom_control=False)

if not add_markers and not add_bboxes:
raise ValueError("At least one of add_markers or add_bboxes must be True.")

for entry in entries:
get_rotated_preview(
entry.latitude,
entry.longitude,
entry.size,
entry.rotation,
map=m,
color="#50b0c3",
fit_bounds=False,
add_tile_layer=False,
add_center_marker=add_markers,
add_center_marker_as_pin=True,
add_click_for_marker=False,
add_bbox=add_bboxes,
)

return m


def get_rotated_preview(
lat: float,
lon: float,
distance: int,
angle: int,
map: folium.Map | None = None,
add_tile_layer: bool = True,
color: str | None = None,
fit_bounds: bool = True,
add_center_marker: bool = True,
add_center_marker_as_pin: bool = False,
add_click_for_marker: bool = True,
add_bbox: bool = True,
) -> folium.Map:
"""Return the path to the HTML file where the OpenStreetMap data is saved.
Arguments:
lat (float): Latitude of the central point.
lon (float): Longitude of the central point.
distance (int): Width of the bounding box in meters.
angle (int): Angle of rotation in degrees.
map (folium.Map | None): Folium map object to add the square polygon to.
If not provided, a new map object will be created.
add_tile_layer (bool): True if the tile layer should be added, False otherwise.
color (str | None): Color of the square polygon.
fit_bounds (bool): True if the map should be fitted to the bounding box, False otherwise.
add_center_marker (bool): True if the center marker should be added, False otherwise.
add_click_for_marker (bool): True if the click for marker should be added, False otherwise.
add_bbox (bool): True if the bounding box should be added, False otherwise.
Returns:
folium.Map: Folium map object.
"""
m = folium.Map(zoom_control=False)
m = map or folium.Map(zoom_control=False)
color = color or get_random_color()

if add_tile_layer:
url = "https://mt0.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}&s=Ga"
folium.TileLayer(
tiles=url, name="satellite", attr="Google", overlay=True, show=False, control=True
).add_to(m)
folium.LayerControl().add_to(m)

if add_bbox:
corners = []
half_diagonal = distance * math.sqrt(2) / 2 # Half the diagonal length of the square
for i in range(4):
theta = math.radians(angle + i * 90 + 45) # Rotate by 45 degrees to get the corners
dx = half_diagonal * math.cos(theta)
dy = half_diagonal * math.sin(theta)
corner_lat = lat + (
dy / 111320
) # Approximate conversion from meters to degrees latitude
corner_lon = lon + (
dx / (111320 * math.cos(math.radians(lat)))
) # Approximate conversion from meters to degrees longitude
corners.append((corner_lat, corner_lon))

folium.Polygon(
locations=corners, color=color, fill=True, fill_opacity=0.1, fill_color=color
).add_to(m)

url = "https://mt0.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}&s=Ga"
folium.TileLayer(
tiles=url, name="satellite", attr="Google", overlay=True, show=False, control=True
).add_to(m)
folium.LayerControl().add_to(m)

corners = []
half_diagonal = distance * math.sqrt(2) / 2 # Half the diagonal length of the square
for i in range(4):
theta = math.radians(angle + i * 90 + 45) # Rotate by 45 degrees to get the corners
dx = half_diagonal * math.cos(theta)
dy = half_diagonal * math.sin(theta)
corner_lat = lat + (dy / 111320) # Approximate conversion from meters to degrees latitude
corner_lon = lon + (
dx / (111320 * math.cos(math.radians(lat)))
) # Approximate conversion from meters to degrees longitude
corners.append((corner_lat, corner_lon))

# Add the square polygon to the map
color = get_random_color()
bbox = get_bbox((lat, lon), distance)
north, south, east, west = bbox
m.fit_bounds([[south, west], [north, east]])
folium.Polygon(
locations=corners, color=color, fill=True, fill_opacity=0.1, fill_color=color
).add_to(m)
folium.ClickForMarker("<b>${lat}, ${lng}</b>").add_to(m)

center = get_center(bbox)
folium.CircleMarker(center, radius=1, color=color, fill=True).add_to(m)
if fit_bounds:
m.fit_bounds([[south, west], [north, east]])

if add_click_for_marker:
folium.ClickForMarker("<b>${lat}, ${lng}</b>").add_to(m)

if add_center_marker:
center = get_center(bbox)
if not add_center_marker_as_pin:
folium.CircleMarker(center).add_to(m)
else:
short_lat_lon = f"{round(lat, 4)}, {round(lon, 4)}"
folium.Marker(
center,
popup=f"<b>Coordinates:</b> {short_lat_lon}\n<b>Size:</b> {distance} m\n<b>Rotation:</b> {angle}°",
).add_to(m)

return m

Expand Down
4 changes: 4 additions & 0 deletions webui/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class Messages:
"available for this game."
)

COVERAGE_INFO = (
"In this section you can generate a global preview of all the generated maps. \n"
)

MOVED = "The app has moved to ➡️➡️➡️ [maps4fs.xyz](https://maps4fs.xyz)"
MAIN_PAGE_COMMUNITY_WARNING = (
"🚜 Hey, farmer! \n"
Expand Down
49 changes: 49 additions & 0 deletions webui/webui.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import streamlit.components.v1 as components
from config import DOCS_DIRECTORY, FAQ_MD, get_mds
from generator.generator import GeneratorUI
from osmp import MapEntry, get_rotated_previews
from streamlit_folium import folium_static
from templates import Messages, video_tutorials
from toolbox import ToolboxUI

from maps4fs.generator.statistics import get_main_settings


class WebUI:
def __init__(self):
Expand All @@ -16,6 +20,7 @@ def __init__(self):
statistics_tab,
step_by_step_tab,
video_tutorials_tab,
coverage_tab,
toolbox_tab,
knowledge_tab,
faq_tab,
Expand All @@ -25,6 +30,7 @@ def __init__(self):
"📊 Statistics",
"🔢 Step by step",
"📹 Video Tutorials",
"🌐 Coverage",
"🧰 Modder Toolbox",
"📖 Knowledge base",
"📝 FAQ",
Expand Down Expand Up @@ -58,6 +64,49 @@ def __init__(self):
f"*{video_tutorial.description}*"
)

with coverage_tab:
st.write(Messages.COVERAGE_INFO)
add_bboxes = st.checkbox("Add bounding boxes", value=True)
add_markers = st.checkbox("Add markers", value=False)
limit = st.number_input("Limit of entries", value=0, min_value=0)

if st.button("Show coverage map"):
try:
entries_json = get_main_settings(
fields=["latitude", "longitude", "size", "rotation"], limit=limit
)

identifiers = []
filtered_entries = []
for entry in entries_json:
lat, lon = entry.get("latitude"), entry.get("longitude")
rotation = entry.get("rotation")
size = entry.get("size")
if lat and lon and rotation and size:
identifier = (lat, lon, rotation, size)
if identifier not in identifiers:
identifiers.append(identifier)
filtered_entries.append(entry)

unique_factor = len(filtered_entries) / len(entries_json) * 100

st.info(
f"Retrievied {len(filtered_entries)} unique entries "
f"from total {len(entries_json)}. \nPercentage of "
f"unique entries: {unique_factor:.2f}%."
)

entries = [MapEntry(**entry) for entry in entries_json]

folium_map = get_rotated_previews(
entries,
add_markers=add_markers,
add_bboxes=add_bboxes,
)
folium_static(folium_map, height=500, width=1000)
except Exception as e:
st.error(f"An error occurred: {e}")

with toolbox_tab:
self.toolbox = ToolboxUI()

Expand Down

0 comments on commit 909ecfb

Please sign in to comment.