213 lines
6.8 KiB
Python
213 lines
6.8 KiB
Python
|
# 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_thunderforest = 'Maps © [i][ref=http://www.thunderforest.com]Thunderforest[/ref][/i], Data © [i][ref=http://www.osm.org/copyright]OpenStreetMap contributors[/ref][/i]'
|
||
|
|
||
|
# 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,
|
||
|
),
|
||
|
"osm-hot": (
|
||
|
0,
|
||
|
0,
|
||
|
19,
|
||
|
"http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
|
||
|
"",
|
||
|
),
|
||
|
"osm-de": (
|
||
|
0,
|
||
|
0,
|
||
|
18,
|
||
|
"http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png",
|
||
|
"Tiles @ OSM DE",
|
||
|
),
|
||
|
"osm-fr": (
|
||
|
0,
|
||
|
0,
|
||
|
20,
|
||
|
"http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||
|
"Tiles @ OSM France",
|
||
|
),
|
||
|
"cyclemap": (
|
||
|
0,
|
||
|
0,
|
||
|
17,
|
||
|
"http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png",
|
||
|
"Tiles @ Andy Allan",
|
||
|
),
|
||
|
"thunderforest-cycle": (
|
||
|
0,
|
||
|
0,
|
||
|
19,
|
||
|
"http://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png",
|
||
|
attribution_thunderforest,
|
||
|
),
|
||
|
"thunderforest-transport": (
|
||
|
0,
|
||
|
0,
|
||
|
19,
|
||
|
"http://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png",
|
||
|
attribution_thunderforest,
|
||
|
),
|
||
|
"thunderforest-landscape": (
|
||
|
0,
|
||
|
0,
|
||
|
19,
|
||
|
"http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png",
|
||
|
attribution_thunderforest,
|
||
|
),
|
||
|
"thunderforest-outdoors": (
|
||
|
0,
|
||
|
0,
|
||
|
19,
|
||
|
"http://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png",
|
||
|
attribution_thunderforest,
|
||
|
),
|
||
|
# no longer available
|
||
|
# "mapquest-osm": (0, 0, 19, "http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg", "Tiles Courtesy of Mapquest", {"subdomains": "1234", "image_ext": "jpeg"}),
|
||
|
# "mapquest-aerial": (0, 0, 19, "http://oatile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpeg", "Tiles Courtesy of Mapquest", {"subdomains": "1234", "image_ext": "jpeg"}),
|
||
|
# more to add with
|
||
|
# https://github.com/leaflet-extras/leaflet-providers/blob/master/leaflet-providers.js
|
||
|
# not working ?
|
||
|
# "openseamap": (0, 0, 19, "http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png",
|
||
|
# "Map data @ OpenSeaMap contributors"),
|
||
|
}
|
||
|
|
||
|
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",
|
||
|
**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.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):
|
||
|
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,
|
||
|
**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)
|