Sideband/sbapp/mapview/source.py

167 lines
5.0 KiB
Python
Raw Normal View History

2023-10-19 07:01:17 -06:00
# coding=utf-8
__all__ = ["MapSource"]
import hashlib
from math import atan, ceil, cos, exp, log, pi, tan
from kivy.metrics import dp
from mapview.constants import (
CACHE_DIR,
MAX_LATITUDE,
MAX_LONGITUDE,
MIN_LATITUDE,
MIN_LONGITUDE,
)
from mapview.downloader import Downloader
from mapview.utils import clamp
class MapSource:
"""Base class for implementing a map source / provider
"""
attribution_osm = 'Maps & Data © [i][ref=http://www.osm.org/copyright]OpenStreetMap contributors[/ref][/i]'
attribution_ve = 'Maps © [i][ref=http://www.virtualearth.net]VirtualEarth[/ref][/i]'
2023-10-19 07:01:17 -06:00
# list of available providers
# cache_key: (is_overlay, minzoom, maxzoom, url, attribution)
providers = {
"osm": (
0,
0,
19,
"http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution_osm,
),
"ve": (
2023-10-19 07:01:17 -06:00
0,
0,
19,
"http://ecn.t3.tiles.virtualearth.net/tiles/a{q}.jpeg?g=1",
attribution_ve,
2023-10-19 07:01:17 -06:00
),
"osm-hot": (
2023-10-19 07:01:17 -06:00
0,
0,
19,
"http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
"",
2023-10-19 07:01:17 -06:00
),
}
def __init__(
self,
url="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
cache_key=None,
min_zoom=0,
max_zoom=19,
tile_size=256,
image_ext="png",
attribution="© OpenStreetMap contributors",
subdomains="abc",
quad_key = False,
2023-10-19 07:01:17 -06:00
**kwargs
):
if cache_key is None:
# possible cache hit, but very unlikely
cache_key = hashlib.sha224(url.encode("utf8")).hexdigest()[:10]
self.url = url
self.cache_key = cache_key
self.min_zoom = min_zoom
self.max_zoom = max_zoom
self.tile_size = tile_size
self.image_ext = image_ext
self.attribution = attribution
self.subdomains = subdomains
self.quad_key = quad_key
2023-10-19 07:01:17 -06:00
self.cache_fmt = "{cache_key}_{zoom}_{tile_x}_{tile_y}.{image_ext}"
self.dp_tile_size = min(dp(self.tile_size), self.tile_size * 2)
self.default_lat = self.default_lon = self.default_zoom = None
self.bounds = None
self.cache_dir = kwargs.get('cache_dir', CACHE_DIR)
@staticmethod
def from_provider(key, **kwargs):
quad_key = kwargs.get('quad_key', False)
2023-10-19 07:01:17 -06:00
provider = MapSource.providers[key]
cache_dir = kwargs.get('cache_dir', CACHE_DIR)
options = {}
is_overlay, min_zoom, max_zoom, url, attribution = provider[:5]
if len(provider) > 5:
options = provider[5]
return MapSource(
cache_key=key,
min_zoom=min_zoom,
max_zoom=max_zoom,
url=url,
cache_dir=cache_dir,
attribution=attribution,
quad_key=quad_key,
2023-10-19 07:01:17 -06:00
**options
)
def get_x(self, zoom, lon):
"""Get the x position on the map using this map source's projection
(0, 0) is located at the top left.
"""
lon = clamp(lon, MIN_LONGITUDE, MAX_LONGITUDE)
return ((lon + 180.0) / 360.0 * pow(2.0, zoom)) * self.dp_tile_size
def get_y(self, zoom, lat):
"""Get the y position on the map using this map source's projection
(0, 0) is located at the top left.
"""
lat = clamp(-lat, MIN_LATITUDE, MAX_LATITUDE)
lat = lat * pi / 180.0
return (
(1.0 - log(tan(lat) + 1.0 / cos(lat)) / pi) / 2.0 * pow(2.0, zoom)
) * self.dp_tile_size
def get_lon(self, zoom, x):
"""Get the longitude to the x position in the map source's projection
"""
dx = x / float(self.dp_tile_size)
lon = dx / pow(2.0, zoom) * 360.0 - 180.0
return clamp(lon, MIN_LONGITUDE, MAX_LONGITUDE)
def get_lat(self, zoom, y):
"""Get the latitude to the y position in the map source's projection
"""
dy = y / float(self.dp_tile_size)
n = pi - 2 * pi * dy / pow(2.0, zoom)
lat = -180.0 / pi * atan(0.5 * (exp(n) - exp(-n)))
return clamp(lat, MIN_LATITUDE, MAX_LATITUDE)
def get_row_count(self, zoom):
"""Get the number of tiles in a row at this zoom level
"""
if zoom == 0:
return 1
return 2 << (zoom - 1)
def get_col_count(self, zoom):
"""Get the number of tiles in a col at this zoom level
"""
if zoom == 0:
return 1
return 2 << (zoom - 1)
def get_min_zoom(self):
"""Return the minimum zoom of this source
"""
return self.min_zoom
def get_max_zoom(self):
"""Return the maximum zoom of this source
"""
return self.max_zoom
def fill_tile(self, tile):
"""Add this tile to load within the downloader
"""
if tile.state == "done":
return
Downloader.instance(cache_dir=self.cache_dir).download_tile(tile)