Source code for inatcog.maps
"""Module to make maps for iNat."""
from collections import namedtuple
import math
from dronefly.core.formatters.constants import WWW_BASE_URL
MapCoords = namedtuple("MapCoords", "zoom_level, center_lat, center_lon")
MapLink = namedtuple("MapLink", "title, url")
[docs]def normalize_longitude(deg):
"""Account for wrap around the globe."""
while deg < 0:
deg += 360
while deg > 360:
deg -= 360
return deg
[docs]def get_zoom_level(swlat, swlng, nelat, nelng):
"""Get zoom level from coordinate pairs."""
west = min(swlng, nelng)
east = max(swlng, nelng)
angle = east - west
north = max(swlat, nelat)
south = min(swlat, nelat)
angle2 = north - south
delta = 0
if angle2 > angle:
angle = angle2
delta = 3
if angle < 0:
angle += 360
if angle == 0:
return 10
result = int(math.log2(394 / angle)) + 2 - delta
if result > 10:
result = 10
if result < 2:
result = 2
return result
[docs]class INatMapURL:
"""Make URL for iNat range map via /taxa/map interface on website."""
def __init__(self, api):
self.api = api
[docs] async def get_map_coords_for_taxon_ids(self, taxon_ids):
"""Get map coordinates encompassing taxa ranges/observations."""
bounds = await self.api.get_observation_bounds(taxon_ids)
if not bounds:
center_lat = 0
center_lon = 0
zoom_level = 2
else:
swlat = bounds["swlat"]
swlng = normalize_longitude(bounds["swlng"])
nelat = bounds["nelat"]
nelng = normalize_longitude(bounds["nelng"])
center_lat = (swlat + nelat) / 2
center_lon = (swlng + nelng) / 2
zoom_level = get_zoom_level(swlat, swlng, nelat, nelng)
return MapCoords(zoom_level, center_lat, center_lon)
[docs] async def get_map_url_for_taxa(self, taxa):
"""Get a map url for taxa from the provided coords."""
taxon_ids = [taxon.id for taxon in taxa]
map_coords = await self.get_map_coords_for_taxon_ids(taxon_ids)
zoom_lat_lon = "/".join(map(str, map_coords))
taxon_ids_str = ",".join(map(str, taxon_ids))
url = f"{WWW_BASE_URL}/taxa/map?taxa={taxon_ids_str}#{zoom_lat_lon}"
return url