671 lines
19 KiB
GDScript
671 lines
19 KiB
GDScript
@tool
|
|
extends CanvasGroup
|
|
class_name MercatorTileMap
|
|
|
|
## Отображение карт WEB Mercator
|
|
|
|
#http://10.1.1.220/osm_tiles/0/0/0.png
|
|
#https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
|
@export var base_url: String = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png':
|
|
set(v):
|
|
if base_url != v:
|
|
base_url = v
|
|
_clean_all()
|
|
queue_redraw()
|
|
|
|
@export var zoom_step: float = 0.025
|
|
@export var tile_width: float = 256.0
|
|
@export var tile_height: float = 256.0
|
|
@export_range(1, 1000, 1) var max_concurrent_requests:int = 5
|
|
@export_range(100, 100000, 1) var max_cached_tiles: int = 200
|
|
@export_range(0, 25, 1) var max_zoom_level: int = 19
|
|
@export_range(0, 25, 1) var min_zoom_level: int = 0
|
|
@export_color_no_alpha() var back_color: = Color.BLACK
|
|
@export var animation_time: float = 1.0
|
|
const default_size: = 400.0
|
|
var is_centered: = false
|
|
|
|
|
|
@export var size: Vector2i = Vector2i(default_size, default_size):
|
|
set(v):
|
|
$canvas.size = v
|
|
_on_resize()
|
|
queue_redraw()
|
|
get:
|
|
return $canvas.size
|
|
|
|
|
|
@export var ax: float = 0.0:
|
|
set(v):
|
|
_xyz.x = v
|
|
queue_redraw()
|
|
get:
|
|
return _xyz.x
|
|
|
|
|
|
@export var ay: float = 0.0:
|
|
set(v):
|
|
_xyz.y = v
|
|
queue_redraw()
|
|
get:
|
|
return _xyz.y
|
|
|
|
|
|
@export var az: float = 0.0:
|
|
set(v):
|
|
_xyz.z = v
|
|
queue_redraw()
|
|
get:
|
|
return _xyz.z
|
|
|
|
|
|
var _xyz: Vector3 = Vector3(0, 0, min_zoom_level)
|
|
var _cache: Dictionary = {}
|
|
var _queue: Dictionary = {}
|
|
var _dragging: bool = false
|
|
var _drag_pos: Vector2 = Vector2.ZERO
|
|
var _last_cache_check: float = 0
|
|
var _rollover: bool = false
|
|
var _marks: Dictionary = {}
|
|
var _req_count: = 0
|
|
var image: = Image.new()
|
|
var image_load_proc: Callable
|
|
var now: = 0
|
|
var visible_marks_count: = 0
|
|
var mouse_wheel_count: = 0
|
|
var map_server_available: = false
|
|
|
|
|
|
## Вызывается после того, как загрузка плиток завершена
|
|
signal load_completed()
|
|
|
|
## Вызывается при запуске загрузки плиток
|
|
signal load_started()
|
|
|
|
## Вызывается при получении ответа от сервера карт
|
|
signal map_server_online()
|
|
|
|
## Вызывается при отсутствии ответа от сервера карт
|
|
signal map_server_offline()
|
|
|
|
const image_load_proc_names = {
|
|
'png': 'load_png_from_buffer',
|
|
'jpg': 'load_jpg_from_buffer',
|
|
'tga': 'load_tga_from_buffer',
|
|
'webp': 'load_webp_from_buffer' }
|
|
|
|
|
|
@export_enum('png', 'jpg', 'jpeg', 'tga', 'webp') var image_type: String = 'png':
|
|
set(v): image_load_proc = Callable(image, image_load_proc_names[v])
|
|
|
|
|
|
func _ready():
|
|
image_type = 'png'
|
|
$canvas.connect('mouse_entered', func(): _rollover = true)
|
|
$canvas.connect('mouse_exited', func(): _rollover = false)
|
|
_on_resize()
|
|
signaller.connect('map_centered', on_map_centered)
|
|
if not Engine.is_editor_hint():
|
|
var key = settings.get_unit_key('навигация')
|
|
var unit_nav = network.units[key]
|
|
unit_nav.connect('data_received', on_data_received)
|
|
|
|
|
|
func on_map_centered(toggle_centered: bool):
|
|
is_centered = toggle_centered
|
|
|
|
|
|
func on_data_received(coords: Dictionary):
|
|
if not is_centered:
|
|
return
|
|
if coords.has_all(['latitude', 'longitude']):
|
|
set_coordinates(coords['longitude'], coords['latitude'])
|
|
|
|
|
|
func _on_resize() -> void:
|
|
var center = $canvas.size / 2.0
|
|
$canvas/spr_mask.position = center
|
|
$canvas/spr_mask.scale = $canvas.size / default_size
|
|
|
|
|
|
## Создаёт новую отметку из координат экрана
|
|
func add_mark_from_screen_pos(id: int, x: float, y: float, mark: Node):
|
|
if _marks.has(id):
|
|
return false
|
|
var wrld_pos = screen_to_world(_xyz, x, y)
|
|
var p = world_to_tile(wrld_pos.x, wrld_pos.y, _xyz.z)
|
|
var tile_index = xyz_to_idx(p.x, p.y, _xyz.z)
|
|
var dot = { 'position': wrld_pos, 'tile_index': tile_index, 'mark': mark }
|
|
_marks[id] = dot
|
|
add_child(mark)
|
|
fix_mark_position(dot)
|
|
return true
|
|
|
|
|
|
func get_marks_count(): return _marks.size()
|
|
|
|
|
|
func delete_mark(id: int):
|
|
if not _marks.has(id):
|
|
return false
|
|
_marks[id].mark.queue_free()
|
|
_marks.erase(id)
|
|
queue_redraw()
|
|
|
|
|
|
## Создаёт новую отметку из долготы и широты
|
|
func add_mark_from_lon_lat(id: int, lon: float, lat: float, mark: Node, gap_size: float = 32):
|
|
if _marks.has(id):
|
|
return false
|
|
var wrld_pos = lonlat_to_world(lon, lat)
|
|
var scr_pos = world_to_screen(wrld_pos.x, wrld_pos.y)
|
|
mark.set_global_position(scr_pos)
|
|
_marks[id] = {'position': wrld_pos, 'mark': mark, 'gap_size': gap_size }
|
|
add_child(mark)
|
|
queue_redraw()
|
|
return true
|
|
|
|
|
|
## Возвращает новую позицию для точки, если она выходит из поля видимости
|
|
func fix_mark_position(dot):
|
|
if is_mark_visible(dot):
|
|
return
|
|
var v = 2 * tile_width * pow(2, _xyz.z)
|
|
if dot.position.x < _xyz.x - $canvas.size.x / v:
|
|
dot.position.x = _xyz.x - $canvas.size.x / v
|
|
elif dot.position.x > _xyz.x + $canvas.size.x / v:
|
|
dot.position.x = _xyz.x + $canvas.size.x / v
|
|
|
|
|
|
func is_mark_visible(dot):
|
|
var screen_pos = world_to_screen(dot.position.x, dot.position.y)
|
|
return screen_pos.x > 0 and screen_pos.x < $canvas.size.x
|
|
|
|
|
|
func _input(e):
|
|
var lmp = get_local_mouse_position()
|
|
if not _rollover:
|
|
return
|
|
if e is InputEventMouseButton:
|
|
if e.pressed:
|
|
if e.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
|
mouse_wheel_count -= 1
|
|
queue_redraw()
|
|
elif e.button_index == MOUSE_BUTTON_WHEEL_UP:
|
|
mouse_wheel_count += 1
|
|
queue_redraw()
|
|
|
|
if e.button_index == MOUSE_BUTTON_LEFT:
|
|
_dragging = e.pressed
|
|
_drag_pos = screen_to_world(_xyz, lmp.x, lmp.y)
|
|
|
|
|
|
elif e is InputEventMouseMotion:
|
|
if _dragging:
|
|
var wp = screen_to_world(_xyz, lmp.x, lmp.y)
|
|
var diff = _drag_pos - wp
|
|
_xyz.x += diff.x
|
|
_xyz.y += diff.y
|
|
queue_redraw()
|
|
if diff.length() > 0:
|
|
signaller.emmit(signaller.map_user_panning)
|
|
|
|
|
|
## Возвращает true, если видна область за краем карты
|
|
func is_border_visible():
|
|
var z = min(max_zoom_level, _xyz.z)
|
|
return screen_to_tile(0, 0, z).y != 0
|
|
|
|
|
|
func multiply_zoom(multiplier: float, pivot: Vector2):
|
|
var p1: = screen_to_world(_xyz, pivot.x, pivot.y)
|
|
_xyz.z = clampf(_xyz.z * multiplier, min_zoom_level, max_zoom_level)
|
|
var p2: = screen_to_world(_xyz, pivot.x, pivot.y)
|
|
_xyz.x -= p2.x - p1.x
|
|
_xyz.y -= p2.y - p1.y
|
|
|
|
|
|
func set_zoom(zoom: float, pivot=null):
|
|
if pivot == null:
|
|
pivot = $canvas.size / 2.0
|
|
|
|
var p1: = screen_to_world(_xyz, pivot.x, pivot.y)
|
|
_xyz.z = clampf(zoom, max_zoom_level, min_zoom_level)
|
|
var p2: = screen_to_world(_xyz, pivot.x, pivot.y)
|
|
_xyz.x -= p2.x - p1.x
|
|
_xyz.y -= p2.y - p1.y
|
|
|
|
|
|
func _draw():
|
|
|
|
var lmp = get_local_mouse_position()
|
|
if mouse_wheel_count > 0:
|
|
multiply_zoom(1.0 + zoom_step, lmp)
|
|
mouse_wheel_count -= 1
|
|
elif mouse_wheel_count < 0:
|
|
multiply_zoom(1.0 - zoom_step, lmp)
|
|
mouse_wheel_count += 1
|
|
|
|
var z = min(max_zoom_level, _xyz.z)
|
|
var t1 = screen_to_tile(0, 0, z)
|
|
var t2 = screen_to_tile($canvas.size.x, $canvas.size.y, z)
|
|
for tx in range(t1.x, t2.x + 1):
|
|
for ty in range(t1.y, t2.y + 1):
|
|
_draw_tile(tx, ty, z)
|
|
|
|
var n = pow(2, z)
|
|
var dx = n * tile_width
|
|
var c = $canvas.size / 2.0
|
|
var r = min($canvas.size.x, $canvas.size.y) / 2.0
|
|
visible_marks_count = 0
|
|
for dot in _marks.values():
|
|
var pos = world_to_screen(dot.position.x, dot.position.y)
|
|
if pos.x >= 0:
|
|
pos.x = fmod(pos.x, dx)
|
|
else:
|
|
pos.x = -pos.x - 1
|
|
pos.x = int(dx - 1) - fmod(pos.x, dx)
|
|
dot.mark.position = pos
|
|
dot.mark.visible = c.distance_to(pos) < (r - dot.gap_size)
|
|
visible_marks_count += int(dot.mark.visible)
|
|
|
|
|
|
func _clean_all():
|
|
_queue.clear()
|
|
_cache.clear()
|
|
_marks.clear()
|
|
_req_count = 0
|
|
for c in get_children().filter(func (item): return not item.is_in_group('gui_items')):
|
|
remove_child(c)
|
|
c.queue_free()
|
|
|
|
|
|
# get the tile from queue with the newest timestamp
|
|
func get_next_in_queue():
|
|
var tile = null
|
|
for idx in _queue:
|
|
var t = _queue.get(idx)
|
|
if not tile or t.t > tile.t:
|
|
tile = t
|
|
return tile
|
|
|
|
|
|
# delete oldest tiles from cache
|
|
func _clean_cache():
|
|
var overflow = _cache.size() - max_cached_tiles
|
|
if overflow <= 0:
|
|
return
|
|
|
|
var vals = _cache.values()
|
|
vals.sort_custom(func(t1, t2): return t1.t < t2.t)
|
|
for i in overflow:
|
|
_cache.erase(vals[i].i)
|
|
|
|
|
|
func _process(delta):
|
|
now += delta
|
|
|
|
# if cache is overflowing - clean it
|
|
if not _last_cache_check or now - _last_cache_check > 5:
|
|
_last_cache_check = now
|
|
_clean_cache()
|
|
|
|
while not _queue.is_empty() and (_req_count < max_concurrent_requests):
|
|
|
|
var tile = get_next_in_queue()
|
|
if not tile:
|
|
break
|
|
|
|
var req = HTTPRequest.new()
|
|
_req_count += 1
|
|
req.set_meta('tile', tile)
|
|
add_child(req)
|
|
req.name = NodePath(str(tile.i))
|
|
|
|
_queue.erase(tile.i)
|
|
req.request_completed.connect(_response.bind(req, tile))
|
|
|
|
if req.request(tile.url) != OK:
|
|
tile.t = now
|
|
remove_child(req)
|
|
req.queue_free()
|
|
_req_count -= 1
|
|
push_error('запрос \"%s\" не отправлен' % tile.url)
|
|
if _queue.is_empty():
|
|
emit_signal('load_completed')
|
|
|
|
|
|
# result, response_code, headers, body
|
|
func _response(result, code, _headers: Array, body: PackedByteArray, req, tile: Tile):
|
|
|
|
_req_count -= 1
|
|
remove_child(req)
|
|
req.queue_free()
|
|
|
|
if result != HTTPRequest.RESULT_SUCCESS:
|
|
if map_server_available:
|
|
map_server_available = false
|
|
emit_signal('map_server_offline')
|
|
_queue[tile.i] = tile
|
|
return
|
|
|
|
if not map_server_available:
|
|
map_server_available = true
|
|
emit_signal('map_server_online')
|
|
|
|
if code == 404:
|
|
push_error('на сервере нет \"%s\"' % tile.url)
|
|
return
|
|
|
|
if Error.OK != image_load_proc.call(body):
|
|
push_error('не удалось получить изображение (неверный формат?) из \"%s\"' % tile.url)
|
|
return
|
|
|
|
# create texture from image and add it to the cache
|
|
tile.texture = ImageTexture.create_from_image(image)
|
|
_cache[tile.i] = tile
|
|
|
|
# redraw the map
|
|
queue_redraw()
|
|
|
|
|
|
func _draw_subtile(tx: int, ty: int, tz: int, origx: int, origy: int, origz: float) -> bool:
|
|
var subtile = get_tile(tx, ty, tz)
|
|
if not subtile:
|
|
return false
|
|
|
|
var p1 = tile_to_screen(origx, origy, origz)
|
|
var p2 = tile_to_screen(origx + 1, origy + 1, origz)
|
|
var x1 = tile_to_screen(tx, ty, tz)
|
|
var x2 = tile_to_screen(tx + 1, ty + 1, tz)
|
|
|
|
var xdiff = x2.x - x1.x
|
|
var xrat1 = (p1.x - x1.x) / xdiff
|
|
var xrat2 = (p2.x - x1.x) / xdiff
|
|
var xwidth = xrat2 - xrat1
|
|
|
|
var ydiff = x2.y - x1.y
|
|
var yrat1 = (p1.y - x1.y) / ydiff
|
|
var yrat2 = (p2.y - x1.y) / ydiff
|
|
var yheight = yrat2 - yrat1
|
|
|
|
var rect = Rect2(xrat1 * tile_width, yrat1 * tile_height, xwidth * tile_width, yheight * tile_height)
|
|
if subtile.texture:
|
|
draw_texture_rect_region(subtile.texture, Rect2(p1, p2 - p1), rect)
|
|
return true
|
|
return false
|
|
|
|
|
|
func _draw_tile(tx: int, ty: int, z: float):
|
|
var tz = floor(z)
|
|
var p1 = tile_to_screen(tx, ty, z)
|
|
var p2 = tile_to_screen(tx + 1, ty + 1, z)
|
|
var tile = get_tile(tx, ty, tz)
|
|
if tile:
|
|
if tile.texture:
|
|
draw_texture_rect(tile.texture, Rect2(p1, p2 - p1), false, Color.WHITE, false)
|
|
else:
|
|
var zzz = tz
|
|
var txx = tx
|
|
var tyy = ty
|
|
while zzz > 1:
|
|
zzz -= 1
|
|
txx = floor(txx / 2.0)
|
|
tyy = floor(tyy / 2.0)
|
|
if _draw_subtile(txx, tyy, zzz, tx, ty,z):
|
|
break
|
|
|
|
|
|
## convert lon/lat to world coords
|
|
func lonlat_to_world(lon: float, lat: float) -> Vector2:
|
|
var x = lon / 180.0
|
|
var latsin = sin(deg_to_rad(lat) * sign(lat))
|
|
var y = (sign(lat) * (log((1.0 + latsin) / (1.0 - latsin)) / 2.0)) / PI
|
|
return Vector2(x, y)
|
|
|
|
|
|
## convert lon/lat to screen coords
|
|
func lonlat_to_screen(lon: float, lat: float) -> Vector2:
|
|
var w = lonlat_to_world(lon, lat)
|
|
return world_to_screen(w.x, w.y)
|
|
|
|
|
|
## convert lon/lat to tile coords
|
|
func lonlat_to_tile(lon: float, lat: float, z: float) -> Vector2:
|
|
var w = lonlat_to_world(lon,lat)
|
|
return world_to_tile(w.x, w.y, z)
|
|
|
|
|
|
## convert world coords to lon/lat
|
|
func world_to_lonlat(wx: float, wy: float) -> Vector2:
|
|
var lon = wx * 180.0
|
|
var lat = rad_to_deg(atan(sinh(wy * PI)))
|
|
return Vector2(lon, lat)
|
|
|
|
|
|
## convert screen coords to lon/lat
|
|
func screen_to_lonlat(sx: float, sy: float) -> Vector2:
|
|
var w = screen_to_world(_xyz, sx, sy)
|
|
return world_to_lonlat(w.x, w.y)
|
|
|
|
|
|
## convert tile coords to lon/lat
|
|
func tile_to_lonlat(tx: float, ty: float, tz: float) -> Vector2:
|
|
var w = tile_to_world(tx, ty, tz)
|
|
return world_to_lonlat(w.x, w.y)
|
|
|
|
|
|
## convert screen coords to world coords
|
|
func screen_to_world(xyz: Vector3, sx: float, sy: float) -> Vector2:
|
|
var n = pow(2.0, xyz.z)
|
|
var span_w = n * tile_width
|
|
var span_h = n * tile_height
|
|
var px = sx - $canvas.size.x / 2 + span_w / 2
|
|
var py = sy - $canvas.size.y / 2 + span_h / 2
|
|
var xr = px / span_w
|
|
var yr = py / span_h
|
|
var x = (xr * 2.0 - 1.0) + xyz.x
|
|
var y = ((-yr * 2.0) + 1.0) + xyz.y
|
|
return Vector2(x, y)
|
|
|
|
|
|
## convert screen coords to tile coords
|
|
func screen_to_tile(sx: float, sy: float, z: float) -> Vector2:
|
|
var world = screen_to_world(_xyz, sx, sy)
|
|
return world_to_tile(world.x, world.y, z)
|
|
|
|
|
|
## convert tile coords to screen coords
|
|
func tile_to_screen(tx: float, ty: float, tz: float) -> Vector2:
|
|
var w = tile_to_world(tx, ty, tz)
|
|
return world_to_screen(w.x, w.y)
|
|
|
|
|
|
## convert tile coords to world coords
|
|
func tile_to_world(tx: float, ty: float, tz: float) -> Vector2:
|
|
var n = pow(2.0, floor(tz))
|
|
var x = (tx / n) * 2.0 - 1.0
|
|
var y = -((ty / n) * 2.0 - 1.0)
|
|
return Vector2(x, y)
|
|
|
|
|
|
## convert world coords to tile coords
|
|
func world_to_tile(wx: float, wy: float, z: float) -> Vector2:
|
|
var n: = pow(2.0, floor(z))
|
|
var tx: = ((wx + 1.0) / 2.0) * n
|
|
var ty: = ((-wy + 1.0) / 2.0) * n
|
|
tx = floor(tx)
|
|
ty = floor(ty)
|
|
return Vector2(tx, ty)
|
|
|
|
|
|
## convert world coords to screen coords
|
|
func world_to_screen(wx: float, wy: float) -> Vector2:
|
|
var n: = pow(2.0, _xyz.z)
|
|
var w: = n * tile_width
|
|
var h: = n * tile_height
|
|
var xr: = ((wx - _xyz.x) + 1.0) / 2.0
|
|
var yr: = (-(wy - _xyz.y) + 1.0) / 2.0
|
|
var x = w * xr - w / 2.0 + $canvas.size.x / 2.0
|
|
var y = h * yr - h / 2.0 + $canvas.size.y / 2.0
|
|
return Vector2(x, y)
|
|
|
|
|
|
## get the tile index from xyz tile coords
|
|
func xyz_to_idx(x: float, y: float, z: float) -> int:
|
|
var i: = (pow(4.0, z) - 1.0) / 3.0
|
|
var n: = pow(2.0, z)
|
|
return int(i + (y * n + x))
|
|
|
|
|
|
func get_tile(x: int, y: int, z: int, create: bool = true) -> Tile:
|
|
|
|
var n = pow(2, z)
|
|
if x >= 0:
|
|
x %= int(n)
|
|
else:
|
|
x = -x - 1
|
|
x = int(n - 1) - (x % int(n))
|
|
|
|
# out of bounds
|
|
if z < 0 or y < 0 or y >= n:
|
|
return null
|
|
|
|
# get tile index
|
|
var idx = xyz_to_idx(x, y, z)
|
|
var tile = null
|
|
|
|
# retrieve from current requests...
|
|
var req = get_node_or_null(NodePath(str(idx)))
|
|
if req:
|
|
tile = req.get_meta('tile')
|
|
tile.t = now
|
|
return tile
|
|
|
|
# retrieve from cache
|
|
tile = _cache.get(idx)
|
|
if tile:
|
|
tile.t = now
|
|
return tile
|
|
|
|
# retrieve from queue
|
|
tile = _queue.get(idx)
|
|
if tile:
|
|
tile.t = now
|
|
return tile
|
|
|
|
# create a new tile - add it to queue
|
|
var queue_size: = _queue.size()
|
|
if create and (not $canvas/player.is_playing()):
|
|
tile = Tile.new(idx, x, y, z)
|
|
tile.url = base_url.replace('{x}', str(x)).replace('{y}', str(y)).replace('{z}', str(z))
|
|
tile.t = now
|
|
_queue[idx] = tile
|
|
|
|
if queue_size == 0 and _queue.size():
|
|
emit_signal('load_started')
|
|
|
|
return tile
|
|
|
|
|
|
func lonlat_to_dms(lon: float, lat: float) -> String:
|
|
var pf = 'N' if lat >= 0 else 'S'
|
|
lat = abs(lat)
|
|
var deg = floor(lat)
|
|
var minute = (lat - deg) * 60
|
|
var second = (minute - floor(minute)) * 60
|
|
var text = ('%02d' % deg) + ('%02d' % floor(minute)) + ('%02d' % floor(second)) + pf + ' '
|
|
|
|
pf = 'E' if lon >= 0 else 'W'
|
|
lon = abs(lon)
|
|
deg = floor(lon)
|
|
minute = (lon - deg) * 60
|
|
second = (minute - floor(minute)) * 60
|
|
text += ('%02d' % deg) + ('%02d' % floor(minute)) + ('%02d' % floor(second)) + pf
|
|
|
|
return text
|
|
|
|
|
|
class Tile:
|
|
var i: int = 0
|
|
var x: int = 0
|
|
var y: int = 0
|
|
var z: int = 0
|
|
var t: int = 0
|
|
var url: String = ''
|
|
var texture: Texture2D = null
|
|
|
|
func _init(i0: int, x0: int, y0: int, z0: int):
|
|
self.i = i0
|
|
self.x = x0
|
|
self.y = y0
|
|
self.z = z0
|
|
|
|
|
|
func haversine_m(lat1, lon1, lat2, lon2):
|
|
# Радиус Земли в метрах
|
|
var R: = 6372795.0
|
|
# Преобразование градусов в радианы
|
|
var rlat1: = deg_to_rad(lat1)
|
|
var rlon1: = deg_to_rad(lon1)
|
|
var rlat2: = deg_to_rad(lat2)
|
|
var rlon2: = deg_to_rad(lon2)
|
|
var cl1: = cos(rlat1)
|
|
var cl2: = cos(rlat2)
|
|
var sl1: = sin(rlat1)
|
|
var sl2: = sin(rlat2)
|
|
var delta: = rlon2 - rlon1
|
|
var cdelta: = cos(delta)
|
|
var sdelta: = sin(delta)
|
|
# вычисления длины большого круга
|
|
var y: = sqrt(pow(cl2 * sdelta, 2) + pow(cl1 * sl2 - sl1 * cl2 * cdelta, 2))
|
|
var x: = sl1 * sl2 + cl1 * cl2 * cdelta
|
|
var ad: = atan2(y, x)
|
|
var dist: = ad * R
|
|
return dist
|
|
|
|
|
|
func remove_closest_dot(screen_pos):
|
|
var closest_dot = find_at_screen_position(screen_pos)
|
|
if not closest_dot:
|
|
return
|
|
remove_child(closest_dot.mark)
|
|
closest_dot.mark.queue_free()
|
|
_marks.erase(closest_dot)
|
|
|
|
|
|
func find_at_screen_position(screen_pos: Vector2):
|
|
var closest_dot = null
|
|
var closest_distance = INF
|
|
for mark in _marks.values():
|
|
var dot_screen_pos = world_to_screen(mark.position.x, mark.position.y)
|
|
var distance = dot_screen_pos.distance_to(screen_pos)
|
|
if distance < closest_distance:
|
|
closest_distance = distance
|
|
closest_dot = mark
|
|
return closest_dot
|
|
|
|
|
|
func clear() -> void:
|
|
_clean_all()
|
|
queue_redraw()
|
|
|
|
|
|
func set_coordinates(lon: float, lat: float):
|
|
var anim: Animation = $canvas/player.get_animation('goto')
|
|
var wrld_pos = lonlat_to_world(lon, lat)
|
|
|
|
anim.track_insert_key(0, 0, _xyz.x)
|
|
anim.track_insert_key(1, 0, _xyz.y)
|
|
|
|
anim.track_insert_key(0, animation_time, wrld_pos.x)
|
|
anim.track_insert_key(1, animation_time, wrld_pos.y)
|
|
|
|
anim.length = animation_time
|
|
$canvas/player.play('goto')
|
|
|
|
|
|
func blend_mask(val: bool) -> void:
|
|
$canvas/spr_mask.visible = val
|
|
queue_redraw()
|