step 1: lock public api and pure core
This commit is contained in:
@@ -1 +1,92 @@
|
||||
"""Web Mercator projection helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from .exceptions import ProjectionError
|
||||
from .types import LatLon, Point
|
||||
|
||||
WEB_MERCATOR_MAX_LAT = 85.05112878
|
||||
DEFAULT_TILE_SIZE = 256
|
||||
|
||||
|
||||
def clamp_latitude(lat: float) -> float:
|
||||
"""Clamp latitude to the Web Mercator representable range."""
|
||||
|
||||
return min(max(lat, -WEB_MERCATOR_MAX_LAT), WEB_MERCATOR_MAX_LAT)
|
||||
|
||||
|
||||
def map_size(zoom: int, tile_size: int = DEFAULT_TILE_SIZE) -> int:
|
||||
"""Return square pixel size of the full world at a zoom level."""
|
||||
|
||||
if zoom < 0:
|
||||
raise ProjectionError("zoom must be >= 0")
|
||||
if tile_size <= 0:
|
||||
raise ProjectionError("tile_size must be > 0")
|
||||
return tile_size * (2**zoom)
|
||||
|
||||
|
||||
def latlon_to_world(lat: float, lon: float, zoom: int, tile_size: int = DEFAULT_TILE_SIZE) -> Point:
|
||||
"""Project latitude/longitude to world pixel coordinates."""
|
||||
|
||||
size = map_size(zoom, tile_size)
|
||||
lat = clamp_latitude(lat)
|
||||
lon = ((lon + 180.0) % 360.0) - 180.0
|
||||
|
||||
sin_lat = math.sin(math.radians(lat))
|
||||
x = (lon + 180.0) / 360.0 * size
|
||||
y = (0.5 - math.log((1.0 + sin_lat) / (1.0 - sin_lat)) / (4.0 * math.pi)) * size
|
||||
return (x, y)
|
||||
|
||||
|
||||
def world_to_latlon(x: float, y: float, zoom: int, tile_size: int = DEFAULT_TILE_SIZE) -> LatLon:
|
||||
"""Unproject world pixel coordinates to latitude/longitude."""
|
||||
|
||||
size = map_size(zoom, tile_size)
|
||||
lon = x / size * 360.0 - 180.0
|
||||
n = math.pi - 2.0 * math.pi * y / size
|
||||
lat = math.degrees(math.atan(math.sinh(n)))
|
||||
return (lat, lon)
|
||||
|
||||
|
||||
def latlon_to_tile(lat: float, lon: float, zoom: int) -> tuple[int, int, int]:
|
||||
"""Return the XYZ tile coordinate containing a latitude/longitude point."""
|
||||
|
||||
x, y = latlon_to_world(lat, lon, zoom)
|
||||
scale = 2**zoom
|
||||
tile_x = min(max(int(x // DEFAULT_TILE_SIZE), 0), scale - 1)
|
||||
tile_y = min(max(int(y // DEFAULT_TILE_SIZE), 0), scale - 1)
|
||||
return (tile_x, tile_y, zoom)
|
||||
|
||||
|
||||
def world_to_screen(
|
||||
world_x: float,
|
||||
world_y: float,
|
||||
*,
|
||||
center: LatLon,
|
||||
zoom: int,
|
||||
width: int,
|
||||
height: int,
|
||||
tile_size: int = DEFAULT_TILE_SIZE,
|
||||
) -> Point:
|
||||
"""Convert world pixels to screen pixels for a centered viewport."""
|
||||
|
||||
center_x, center_y = latlon_to_world(center[0], center[1], zoom, tile_size)
|
||||
return (world_x - center_x + width / 2.0, world_y - center_y + height / 2.0)
|
||||
|
||||
|
||||
def screen_to_world(
|
||||
screen_x: float,
|
||||
screen_y: float,
|
||||
*,
|
||||
center: LatLon,
|
||||
zoom: int,
|
||||
width: int,
|
||||
height: int,
|
||||
tile_size: int = DEFAULT_TILE_SIZE,
|
||||
) -> Point:
|
||||
"""Convert screen pixels to world pixels for a centered viewport."""
|
||||
|
||||
center_x, center_y = latlon_to_world(center[0], center[1], zoom, tile_size)
|
||||
return (screen_x + center_x - width / 2.0, screen_y + center_y - height / 2.0)
|
||||
|
||||
Reference in New Issue
Block a user