В процессе. Карта отображается. Ошибка: накапливаются запросы на получение карты.
This commit is contained in:
@@ -2,12 +2,14 @@ extends Node
|
||||
|
||||
## Веб-утилиты для работы с меркаторскими XYZ-плитками.
|
||||
|
||||
const R2D: = 180.0 / PI
|
||||
const RE: = 6378137.0
|
||||
const CE: = 2.0 * PI * RE
|
||||
const EPSILON: = 1e-14
|
||||
const LL_EPSILON: = 1e-11
|
||||
const MAX_ZOOM: = 28
|
||||
const R2D: = 180.0 / PI
|
||||
const RE: = 6378137.0 ## Радиус Земли в метрах
|
||||
const CE: = 2.0 * PI * RE ## Длина окружности Земли в метрах
|
||||
const EPSILON: = 1e-14
|
||||
const LL_EPSILON: = 1e-11
|
||||
const MAX_ZOOM: = 20
|
||||
const MAX_NORTH = 85.051129
|
||||
const MAX_SOUTH = -85.051129
|
||||
|
||||
|
||||
## XYZ-плитка веб-меркатора [param x], [param y], [param z] индексы
|
||||
@@ -16,15 +18,19 @@ class Tile:
|
||||
var x: int
|
||||
var y: int
|
||||
var z: int
|
||||
var lng: float ## Координаты, которые плитка должна покрывать, долгота, градусы
|
||||
var lat: float ## Широта, градусы
|
||||
var is_root: bool ## Признак, что плитка корневая (покрывает координаты)
|
||||
|
||||
func as_array(): return [self.x, self.y, self.z]
|
||||
|
||||
func _to_string() -> String: return '[%d, %d, %d]' % [self.x, self.y, self.z]
|
||||
|
||||
func _init(x0: int = 0, y0: int = 0, z0: int = 0):
|
||||
func _init(x0: int = 0, y0: int = 0, z0: int = 0, is_root0: bool = true):
|
||||
self.x = x0
|
||||
self.y = y0
|
||||
self.z = z0
|
||||
self.is_root = is_root0
|
||||
|
||||
func is_valid() -> bool:
|
||||
if (self.z >= 0) and (self.z < MAX_ZOOM):
|
||||
@@ -38,8 +44,12 @@ class Tile:
|
||||
class LngLat:
|
||||
var lng: float = 0.0 ## Долгота в десятичных градусах восточной долготы.
|
||||
var lat: float = 0.0 ## Широта в десятичных градусах северной широты.
|
||||
|
||||
## Возвращает в виде массива
|
||||
func as_array() -> Array: return [self.lng, self.lat]
|
||||
|
||||
func _to_string() -> String: return '[%f, %f, %f]' % [self.x, self.y, self.z]
|
||||
|
||||
func _init(lng0, lat0):
|
||||
self.lng = lng0
|
||||
self.lat = lat0
|
||||
@@ -51,8 +61,12 @@ class LngLatBbox:
|
||||
var south: float ## Юг
|
||||
var east: float ## Восток
|
||||
var north: float ## Север
|
||||
|
||||
## Возвращает в виде массива
|
||||
func as_array() -> Array: return [self.west, self.south, self.east, self.north]
|
||||
|
||||
func _to_string() -> String: return '[%f, %f, %f, %f]' % [self.west, self.south, self.east, self.north]
|
||||
|
||||
func _init(w: float = 0.0, s: float = 0.0, e: float = 0.0, n: float = 0.0):
|
||||
self.west = w
|
||||
self.south = s
|
||||
@@ -83,6 +97,21 @@ func radians(v: float) -> float: return (v * PI) / 180.0
|
||||
func rshift(val: int, n: int) -> int: return (val % 0x100000000) >> n
|
||||
|
||||
|
||||
## Расстояние по горизонтали в метрах, представленное одним пикселем
|
||||
## [paramn lat] - Широта, градусы.[br]
|
||||
## [paramn zoom] - Уровень детализации.[br]
|
||||
## [paramn size] - Размер плитки в пикселях.[br]
|
||||
func dist_per_pixel(lat: float, zoom: int, size: int = 256) -> float:
|
||||
return (CE / float(size)) * cos(radians(lat)) / float(2 ** zoom)
|
||||
|
||||
|
||||
## Расстояние по горизонтали в метрах, представленное одной плиткой
|
||||
## [paramn lat] - Широта, градусы.[br]
|
||||
## [paramn zoom] - Уровень детализации.[br]
|
||||
func dist_per_tile(lat: float, zoom: int) -> float:
|
||||
return CE * cos(radians(lat)) / float(2 ** zoom)
|
||||
|
||||
|
||||
## Возвращает плитки и занимаемый ими размер области в пикселях:
|
||||
## ширина, высота, левая граница, верхняя граница, плитки.[br]
|
||||
## [param west] - Западная граница области.[br]
|
||||
@@ -130,6 +159,7 @@ func get_degrees_bounds(m: float, lng: float, lat: float) -> Array[float]:
|
||||
return [x_y[0] - m, x_y[1] - m, x_y[0] + m, x_y[1] + m]
|
||||
|
||||
|
||||
## Возвращает границы плиток в градусах
|
||||
func get_tiles_bounds(tiles: Array[Tile]) -> Array:
|
||||
var mm = []
|
||||
for t in tiles: mm.append(bounds(t).west)
|
||||
@@ -182,17 +212,17 @@ func ul2(tile: Tile) -> Array[float]:
|
||||
## Возвращает граничную область плитки.[br]
|
||||
## [param tile] - Плитка.[br]
|
||||
func bounds(tile: Tile) -> LngLatBbox:
|
||||
var Z2: = pow(2, tile.z)
|
||||
var ul_lon_deg = tile.x / Z2 * 360.0 - 180.0
|
||||
var ul_lat_rad = atan(sinh(PI * (1 - 2 * tile.y / Z2)))
|
||||
var z2: = pow(2, tile.z)
|
||||
var ul_lon_deg = tile.x / z2 * 360.0 - 180.0
|
||||
var lr_lon_deg = (tile.x + 1) / z2 * 360.0 - 180.0
|
||||
var ul_lat_rad = atan(sinh(PI * (1 - 2 * tile.y / z2)))
|
||||
var ul_lat_deg = degrees(ul_lat_rad)
|
||||
var lr_lon_deg = (tile.x + 1) / Z2 * 360.0 - 180.0
|
||||
var lr_lat_rad = atan(sinh(PI * (1 - 2 * (tile.y + 1) / Z2)))
|
||||
var lr_lat_rad = atan(sinh(PI * (1 - 2 * (tile.y + 1) / z2)))
|
||||
var lr_lat_deg = degrees(lr_lat_rad)
|
||||
return LngLatBbox.new(ul_lon_deg, lr_lat_deg, lr_lon_deg, ul_lat_deg)
|
||||
|
||||
|
||||
func truncate_lnglat(lng: float, lat: float) -> Array:
|
||||
func truncate_lnglat(lng: float, lat: float) -> Array[float]:
|
||||
if lng > 180.0: lng = 180.0
|
||||
elif lng < -180.0: lng = -180.0
|
||||
if lat > 90.0: lat = 90.0
|
||||
@@ -200,7 +230,7 @@ func truncate_lnglat(lng: float, lat: float) -> Array:
|
||||
return [lng, lat]
|
||||
|
||||
|
||||
## Преобразование долготы и широты в веб-меркатор x, y.[br]
|
||||
## Преобразование долготы и широты в веб-меркатор x, y в метрах.[br]
|
||||
## [param lng] - Долгота в десятичных градусах.[br]
|
||||
## [param lat] - Широта в десятичных градусах.[br]
|
||||
## [param truncate] - Нужно ли усекать входные данные до пределов веб-меркатора.[br]
|
||||
@@ -234,14 +264,17 @@ func lnglat(x: float, y: float, truncate: bool = false) -> LngLat:
|
||||
## >>> neighbors(Tile(486, 332, 10))
|
||||
## [Tile(x=485, y=331, z=10), Tile(x=485, y=332, z=10), Tile(x=485, y=333, z=10), Tile(x=486, y=331, z=10), Tile(x=486, y=333, z=10), Tile(x=487, y=331, z=10), Tile(x=487, y=332, z=10), Tile(x=487, y=333, z=10)]
|
||||
## [/codeblock]
|
||||
func neighbors(tile: Tile) -> Array[Tile]:
|
||||
var tiles: = Array()
|
||||
var lo_hi = [0, 2 ** tile.z - 1]
|
||||
func neighbors(tile: Tile) -> Array:
|
||||
var tiles: = []
|
||||
var hi = 2 ** tile.z - 1
|
||||
for i in [-1, 0, 1]:
|
||||
for j in [-1, 0, 1]:
|
||||
if i == 0 and j == 0: continue
|
||||
elif tile.x + i < 0 or tile.y + j < 0: continue
|
||||
elif tile.x + i > lo_hi[1] or tile.y + j > lo_hi[i]: continue
|
||||
if i == 0 and j == 0:
|
||||
continue
|
||||
elif (tile.x + i) < 0 or (tile.y + j) < 0:
|
||||
continue
|
||||
elif (tile.x + i) > hi or (tile.y + j) > hi:
|
||||
continue
|
||||
var t = Tile.new(tile.x + i, tile.y + j, tile.z)
|
||||
if t.is_valid():
|
||||
tiles.append(t)
|
||||
@@ -260,9 +293,9 @@ func xy_bounds(tile: Tile, zoom: int) -> Bbox:
|
||||
return Bbox.new(left, bottom, right, top)
|
||||
|
||||
|
||||
func _xy(lng, lat, truncate=false) -> Array:
|
||||
func _xy(lng, lat, truncate=false) -> Array[float]:
|
||||
var ll: = [lng, lat] if not truncate else truncate_lnglat(lng, lat)
|
||||
var x = ll[0] / 360.0 + 0.5
|
||||
var x: float = ll[0] / 360.0 + 0.5
|
||||
var sinlat: = sin(radians(ll[1]))
|
||||
var y: = 0.5 - 0.25 * log((1.0 + sinlat) / (1.0 - sinlat)) / PI
|
||||
return [x, y]
|
||||
@@ -274,25 +307,27 @@ func _xy(lng, lat, truncate=false) -> Array:
|
||||
## [param truncate] - Нужно ли усекать вводимые данные до границ веб-меркатора.[br]
|
||||
func create_tile(lng: float, lat: float, zoom: int, truncate: bool=false) -> Tile:
|
||||
var xy0: = _xy(lng, lat, truncate)
|
||||
var Z2 = pow(2, zoom)
|
||||
var z2 = pow(2, zoom)
|
||||
|
||||
# Чтобы устранить потерю точности при переходе от одной плитки к другой
|
||||
# и lng/lat, точки в пределах EPSILON от правой стороны тайла
|
||||
# и lng/lat, точки в пределах EPSILON от правой стороны плитки
|
||||
# считаются в следующей за ней плитке.
|
||||
var tile: = Tile.new()
|
||||
if xy0[0] <= 0: tile.x = 0
|
||||
elif xy0[0] >= 1: tile.x = int(Z2 - 1)
|
||||
else: tile.x = int(floor((xy0[0] + EPSILON) * Z2))
|
||||
elif xy0[0] >= 1: tile.x = int(z2 - 1)
|
||||
else: tile.x = int(floor((xy0[0] + EPSILON) * z2))
|
||||
|
||||
if xy0[1] <= 0: tile.y = 0
|
||||
elif xy0[1] >= 1: tile.y = int(Z2 - 1)
|
||||
else: tile.y = int(floor((xy0[1] + EPSILON) * Z2))
|
||||
elif xy0[1] >= 1: tile.y = int(z2 - 1)
|
||||
else: tile.y = int(floor((xy0[1] + EPSILON) * z2))
|
||||
tile.z = zoom
|
||||
tile.lng = lng
|
||||
tile.lat = lat
|
||||
return tile
|
||||
|
||||
|
||||
## Получение четверного ключа плитки.[br]
|
||||
## [param tile] Плитка
|
||||
## [param tile] - Плитка
|
||||
func quadkey(tile: Tile, zoom: int) -> String:
|
||||
var rs: = ''
|
||||
for z in range(zoom, 0, -1):
|
||||
@@ -337,9 +372,7 @@ func quadkey_to_tile(qk: Array) -> Tile:
|
||||
## [param truncate] [bool] - Нужно ли усекать вводимые данные до границ веб-меркатора.[br]
|
||||
## [i]В параметрах south и east используется небольшой эпсилон, чтобы
|
||||
## функция выдавала ровно одну плитку при задании границ этой же плитки.[/i]
|
||||
func create_tiles(west: float, south: float, east: float, north: float, zooms, truncate: bool=false) -> Array[Tile]:
|
||||
var west_south = [west, south] if truncate else truncate_lnglat(west, south)
|
||||
var east_north = [east, north] if truncate else truncate_lnglat(east, north)
|
||||
func create_tiles(west: float, south: float, east: float, north: float, zooms) -> Array[Tile]:
|
||||
var bboxes = [[west, south, east, north]] if west <= east else [[-180.0, south, east, north], [west, south, 180.0, north]]
|
||||
var tiles = []
|
||||
if not is_instance_of(zooms, TYPE_ARRAY):
|
||||
@@ -351,7 +384,7 @@ func create_tiles(west: float, south: float, east: float, north: float, zooms, t
|
||||
var n = box[3]
|
||||
# Зажим граничных значений.
|
||||
w = max(-180.0, w)
|
||||
s = max(-85.051129, s)
|
||||
s = max(MAX_SOUTH, s)
|
||||
e = min(180.0, e)
|
||||
n = min(85.051129, n)
|
||||
for z in zooms:
|
||||
@@ -412,8 +445,7 @@ func parent(tile: Tile, zoom=null) -> Tile:
|
||||
## >>> children(Tile(0, 0, 0), zoom=2)
|
||||
## [Tile(x=0, y=0, z=2), Tile(x=0, y=1, z=2), Tile(x=0, y=2, z=2), Tile(x=0, y=3, z=2), ...]
|
||||
## [/codeblock]
|
||||
func children(tile, zoom) -> Array:
|
||||
assert((tile.z <= zoom) and zoom == int(zoom), 'Масштаб (zoom) должен быть целым числом и превышать масштаб входной плитки (tile)')
|
||||
func children(tile: Tile, zoom: int) -> Array:
|
||||
var target_zoom = zoom if zoom != null else tile.z + 1
|
||||
var tiles: Array[Tile] = [tile]
|
||||
while tiles[0].z >= target_zoom:
|
||||
@@ -484,33 +516,26 @@ func simplify(tiles: Array) -> Array:
|
||||
## Tile(x=0, y=0, z=0).
|
||||
## [param bbox] последовательность [float] значений границ запада, юга, востока,
|
||||
## севера в десятичных градусах.
|
||||
func bounding_tile(bbox, truncate: bool = false) -> Tile:
|
||||
if len(bbox) == 2:
|
||||
bbox += bbox
|
||||
|
||||
var w = bbox[0]
|
||||
var s = bbox[1]
|
||||
var e = bbox[2]
|
||||
var n = bbox[3]
|
||||
func bounding_tile(bbox: Bbox, truncate: bool = false) -> Tile:
|
||||
|
||||
if truncate:
|
||||
var w_s = truncate_lnglat(w, s)
|
||||
w = w_s[0]
|
||||
s = w_s[1]
|
||||
var e_n = truncate_lnglat(e, n)
|
||||
n = e_n[0]
|
||||
e = e_n[1]
|
||||
var w_s = truncate_lnglat(bbox.left, bbox.bottom)
|
||||
bbox.left = w_s[0]
|
||||
bbox.bottom = w_s[1]
|
||||
var e_n = truncate_lnglat(bbox.right, bbox.north)
|
||||
bbox.right = e_n[0]
|
||||
bbox.top = e_n[1]
|
||||
|
||||
e -= LL_EPSILON
|
||||
s += LL_EPSILON
|
||||
bbox.right -= LL_EPSILON
|
||||
bbox.bottom += LL_EPSILON
|
||||
|
||||
var tmin = create_tile(w, n, 32)
|
||||
var tmax = create_tile(e, s, 32)
|
||||
var tmin = create_tile(bbox.left, bbox.top, 32)
|
||||
var tmax = create_tile(bbox.right, bbox.bottom, 32)
|
||||
assert(tmin.is_valid())
|
||||
assert(tmax.is_valid())
|
||||
|
||||
var cell = [tmin.x, tmin.y, tmax.x, tmax.y]
|
||||
var z = _getBboxZoom(cell)
|
||||
var z = _get_bbox_zoom(cell)
|
||||
|
||||
if z == 0:
|
||||
return Tile.new()
|
||||
@@ -521,7 +546,7 @@ func bounding_tile(bbox, truncate: bool = false) -> Tile:
|
||||
return Tile.new(x, y, z)
|
||||
|
||||
|
||||
func _getBboxZoom(bbox: Array):
|
||||
func _get_bbox_zoom(bbox: Array):
|
||||
for z in MAX_ZOOM:
|
||||
var mask: int = 1 << (32 - (z + 1))
|
||||
if (bbox[0] & mask) != (bbox[2] & mask) or (bbox[1] & mask) != (bbox[3] & mask):
|
||||
|
||||
Reference in New Issue
Block a user