diff --git a/docs/Background/geography/loot_locations.md b/docs/Background/geography/loot_locations.md index dc5fd14..844a990 100644 --- a/docs/Background/geography/loot_locations.md +++ b/docs/Background/geography/loot_locations.md @@ -13,104 +13,4 @@ included in the list solely to contain a geotag to be used on the auto-rendered Super Markets are a great place to find food, medicine, and other useful items. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index aa68d30..7a0499b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -114,11 +114,16 @@ plugins: - search hooks: + - render_map/auto_populate/auto_populate_hooks.py - render_map/plugin_hooks.py extra: # GOOGLE_MAPS_API_KEY: !ENV GOOGLE_MAPS_API_KEY # ReadTheDocs doesn't support loading from env variables! GOOGLE_MAPS_API_KEY: AIzaSyD6gTw8OwC-LyQi3wl_gr8P385pPBfa5tE + # Center and default zoom level for the Google Maps global_map: center: [29.7604, -95.3698] - zoom: 9 \ No newline at end of file + zoom: 9 + auto_populate: + center: [29.7604, -95.3698] + population_radius: 35000 # Radius around the center to auto-populate locations (in meters) \ No newline at end of file diff --git a/render_map/auto_populate/__init__.py b/render_map/auto_populate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/render_map/auto_populate/auto_populate_hooks.py b/render_map/auto_populate/auto_populate_hooks.py new file mode 100644 index 0000000..a947481 --- /dev/null +++ b/render_map/auto_populate/auto_populate_hooks.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import mkdocs.plugins + +from render_map.auto_populate import auto_populate_map + +@mkdocs.plugins.event_priority(100) +def on_page_markdown( + markdown: str, + page: mkdocs.plugins.Page, + config: mkdocs.plugins.MkDocsConfig, + **kwargs, +): + """Find all geotags in the markdown and process them into a list of GeoLink objects. + + Args: + markdown: The markdown to search for geotags. + page: The page object. + config: The mkdocs config. + + Returns: + A list of GeoLink objects. + """ + # Skip, if page is excluded + if page.file.inclusion.is_excluded(): + return + + latitude, longitude = config.extra["auto_populate"]["center"] + radius = config.extra["auto_populate"]["population_radius"] + + geolinks, markdown = auto_populate_map.find_auto_populate_geotags(markdown, latitude, longitude, radius) + + return markdown + diff --git a/render_map/auto_populate_map.py b/render_map/auto_populate/auto_populate_map.py similarity index 54% rename from render_map/auto_populate_map.py rename to render_map/auto_populate/auto_populate_map.py index 8ba256c..6931751 100644 --- a/render_map/auto_populate_map.py +++ b/render_map/auto_populate/auto_populate_map.py @@ -1,7 +1,10 @@ """Automatically populate the map with supermarkets and other landmarks using the Overpass (Open Street Map) API.""" from __future__ import annotations +import bs4 import overpy +import pydantic + from render_map import mapping RADIUS=35000 @@ -21,6 +24,65 @@ API = overpy.Overpass() +class AutoPopulateConfig(pydantic.BaseModel): + """The configuration for the auto-populate plugin.""" + + supermarket: bool = False + + @staticmethod + def tag_name() -> str: + """The name of the tag to search for.""" + return "populate_geotag" + + @classmethod + def from_dict(cls, config_dict: dict[str, str|bool]) -> AutoPopulateConfig: + """Create a config object from a dictionary. + + Args: + config_dict: The dictionary to create the config object from. + + Returns: + A config object. + """ + for key in cls.model_fields: + if key in config_dict: + config_dict[key] = True + return cls(**config_dict) + +def find_auto_populate_geotags(markdown: str, latitude:float, longitude:float, radius:float) -> tuple[list[AutoPopulateConfig], str]: + """Find all geotags in the markdown and process them into a list of GeoLink objects. + + Args: + markdown: The markdown to search for geotags. + latitude: The latitude to search for supermarkets. + longitude: The longitude to search for supermarkets. + radius: The radius in meters to search for supermarkets. + + Returns: + A list of GeoLink objects and the markdown with the geotags replaced. + """ + soup = bs4.BeautifulSoup(markdown, "html.parser") + geo_tags = soup.find_all(AutoPopulateConfig.tag_name()) + + populate_geotags_configs = [] + for geo_tag in geo_tags: + result = geo_tag.attrs + + # Generate a GeoLink object from the result + geotag_config = AutoPopulateConfig.from_dict(result) + populate_geotags_configs.append(geotag_config) + + # Replace the geotag with a span tag with the uuid as the id and the name as the text + new_tag = soup.new_tag("div") + if geotag_config.supermarket: + supermarkets_tags = populate_supermarkets(radius, latitude, longitude) + for tag in supermarkets_tags: + new_tag.append(tag) + new_tag.append(", ") + geo_tag.replace_with(new_tag) + return populate_geotags_configs, str(soup) + + def choose_supermarket_name_zoom(node:overpy.Node) -> tuple[str|None, mapping.ZoomLevel]: """Choose the game name and map zoom level for a supermarket, based on the properties of the supermarket in the real world. @@ -43,7 +105,7 @@ def choose_supermarket_name_zoom(node:overpy.Node) -> tuple[str|None, mapping.Zo return "Supermarket", mapping.ZoomLevel.TOWN -def populate_supermarkets(radius:float, latitude:float, longitude:float) -> str: +def populate_supermarkets(radius:float, latitude:float, longitude:float) -> list[bs4.Tag]: """Generate geotags for supermarkets in the game world, using the locations of supermarkets in the real world (using the Overpass API). @@ -67,4 +129,4 @@ def populate_supermarkets(radius:float, latitude:float, longitude:float) -> str: ) ) - return "\n".join(geotag.get_tag() for geotag in geotags) \ No newline at end of file + return [geotag.get_tag() for geotag in geotags] diff --git a/render_map/mapping.py b/render_map/mapping.py index b8c3fd7..dd9bac4 100644 --- a/render_map/mapping.py +++ b/render_map/mapping.py @@ -54,7 +54,7 @@ class GeoLink(pydantic.BaseModel): zoom: ZoomLevel = pydantic.Field(default=ZoomLevel.WASTELAND, validate_default=True) uuid: str = pydantic.Field(default_factory=lambda: str(uuid.uuid4())) - def get_tag(self, include_uuid:bool=False) -> str: + def get_tag(self, include_uuid:bool=False) -> bs4.Tag: """Get the geotag as a string. Args: @@ -68,12 +68,12 @@ def get_tag(self, include_uuid:bool=False) -> str: tag["name"] = self.name tag["latitude"] = str(self.latitude) tag["longitude"] = str(self.longitude) - tag["icon"] = self.icon.name - tag["zoom"] = str(self.zoom.name) + tag["icon"] = str(map_icons.MapIcon(self.icon).name) + tag["zoom"] = str(ZoomLevel(self.zoom).name) if include_uuid: tag["uuid"] = self.uuid - return str(tag) + return tag GEO_LINKS: list[GeoLink] = []