@@ -1,173 +1,670 @@
class_name main_tilemap extends Sprite2D
@ tool
extends CanvasGroup
## Возможные состояния конечного автомата заданий.
enum TASK_STATE {
DONE , ## Выполнено.
WAIT , ## Ожидание плитки.
RUN , ## Запуск выполнения.
CHECK , ## Проверка пустого места.
ERROR ## Ошибка.
}
## Отображение карт WEB Mercator
const MIN_ZOOM : float = 2.0
const MAX_ZOOM : float = 19.0
const TEST_COORDS : = [ Vector2 ( 0 , 0 ) , Vector2 ( 1 , 0 ) , Vector2 ( 0 , 1 ) , Vector2 ( 1 , 1 ) ]
var test_coords_i : = 3
## Координаты для проверок [lng, lat].
const TEST_DATA : = {
' Калиниград ' : [ 20.51095 , 54.70649 ] ,
' Москва ' : [ 37.6156 , 55.7522 ] ,
' Нью-Йорк ' : [ - 74.006 , 40.7143 ] ,
' Сидней ' : [ 151.20732 , - 33.8678500 ] ,
' Таганрог ' : [ 38.8969 , 47.2362 ] ,
' Хельсинки ' : [ 24.9354 , 60.1695 ] ,
' Азорские о -ва ' : [ 28.3996582 , 38.5397568 ] ,
' Мехико ' : [ - 99.12766 , 19.42847 ] ,
' Белое море ' : [ 68.136606 , 42.045546 ] ,
' море Беллинсгаузена ' : [ - 72.406587 , - 74.857997 ] ,
' Фиджи ' : [ 178.065032 , - 17.713371 ] ,
' Ноль ' : [ 0.0 , 0.0 ] ,
' Т о чка 1' : [ 170.0 , 0.0 ] ,
' Т о чка 2' : [ - 170.0 , 0.0 ] ,
' М о р е ' : [ 19.4 , 54.91 ] }
#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 = 21
@ export_color_no_alpha ( ) var back_color : = Color . BLACK
@ export var animation_time : float = 1.0
const MAP_MODES : = {
0 : ' application/config/map_image_size_rto ' ,
1 : ' application/config/map_image_size_rls ' }
@ export var size : Vector2i = Vector2i ( 400 , 400 ) :
set ( v ) :
$ canvas . size = v
_on_resize ( )
queue_redraw ( )
get :
return $ canvas . size
const MAP_RELATIVE_RADIUSES : = {
0 : 0.39 ,
1 : 0.466
}
@ export var ax : float = 0.0 :
set ( v ) :
_xyz . x = v
queue_redraw ( )
get :
return _xyz . x
var map_image : Image ## Изображение с картой.
var map_tile_state : TASK_STATE ## Текущее состояние конечного автомата плитки.
var map_bcs : Vector3 ## Яркость, контраст насыщенность.
var map_server_addr : String ## Адрес сервера изображений карт в проекции Меркатора.
var map_tile_format : String ##
var map_tile_stack : Array ## Стек для хранения плиток карты.
var map_request : HTTPRequest ## Обработчик запросов http.
var map_zoom : int ## Уровень детализации карты.
var map_tile : Rect2i ## Область одной плитки: [x-координата области, y-координата области, x-индекс плитки, y-индекс плитки].
var map_mode : int ## Режим карты: 0 - приоритет Р Т О , 1 - приоритет РЛС.
var prev_tiles_dic : Dictionary ##
var lnglat : Array ## Текущие координаты.
var root_rect : Rect2i
@ 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 )
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 spr_mask : = Sprite2D . new ( )
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 ( ) :
lnglat = TEST_DATA [ ' Таганрог ' ]
map_tile_state = TASK_STATE . DONE
map_request = HTTPRequest . new ( )
add_child ( map_request )
map_request . request_completed . connect ( self . _map_request_completed )
map_bcs = ProjectSettings . get_setting ( ' application/config/map_bcs ' , Vector3 ( 1.0 , 1.0 , 1.0 ) )
map_server_addr = ProjectSettings . get_setting ( ' application/config/map_server_addr ' )
map_tile_format = ProjectSettings . get_setting ( ' application/config/map_tile_format ' , ' %s / %d / %d / %d .png ' )
var map_image_size = ProjectSettings . get_setting ( MAP_MODES [ 0 ] , Vector2i ( 512 , 512 ) )
var map_tile_size = ProjectSettings . get_setting ( ' application/config/map_tile_size ' , 256 )
map_image = Image . create ( map_image_size . x , map_image_size . y , true , Image . FORMAT_RGB8 )
map_tile = Rect2i ( 0 , 0 , map_tile_size , map_tile_size )
signaller . connect ( ' zoom_changed ' , Callable ( self , ' _on_zoom_changed ' ) )
signaller . connect ( ' map_mode_changed ' , Callable ( self , ' _on_map_mode_changed ' ) )
log . info ( ' Адрес сервера поставщика карт: \" %s \" ' % map_server_addr )
log . info ( ' Модуль географической карты готов ' )
$ canvas . connect ( ' mouse_entered ' , func ( ) : _rollover = true )
$ canvas . connect ( ' mouse_exited ' , func ( ) : _rollover = false )
_on_resize ( )
image_type = ' png '
spr_mask . name = StringName ( ' spr_mask ' )
spr_mask . material = CanvasItemMaterial . new ( )
spr_mask . material . blend_mode = CanvasItemMaterial . BLEND_MODE_SUB
spr_mask . add_to_group ( ' gui_items ' )
$ canvas . add_child ( spr_mask )
func _on_map_mode_changed ( mode : int ) - > void :
var map_image_size = ProjectSettings . get_setting ( MAP_MODES [ mode ] )
material . set ( ' shader_parameter/radius ' , MAP_RELATIVE_RADIUSES [ mode ] )
map_image . fill ( Color ( 0 , 0 , 0 , 0 ) )
map_image . resize ( map_image_size . x , map_image_size . y )
map_tile_state = TASK_STATE . DONE
map_mode = mode
var tile : = mercator . create_tile ( lnglat [ 0 ] , lnglat [ 1 ] , map_zoom )
map_tile_stack . clear ( )
map_tile_stack . append ( tile )
prev_tiles_dic . clear ( )
func _on_resize ( ) - > void :
var img_mask : = Image . create ( $ canvas . size . x , $ canvas . size . y , false , Image . FORMAT_RGBA8 )
var c = $ canvas . size / 2.0
var r = min ( $ canvas . size . x , $ canvas . size . y ) / 2.0
var color = Color . WHITE
var sz = img_mask . get_size ( )
for x in sz . x :
for y in sz . y :
var dist = c . distance_to ( Vector2 ( x , y ) )
color . a = smoothstep ( r - 2.0 , r + 2.0 , dist )
img_mask . set_pixel ( x , y , color )
spr_mask . texture = ImageTexture . create_from_image ( img_mask )
spr_mask . position = $ canvas . position + $ canvas . size / 2.0
func mark_image_center ( image : Image , color : Color = Color . RED ) :
var c = i mage . get_size ( ) / 2
map_image . set_pixel ( c . x - 1 , c . y - 1 , color )
map_image . set_pixel ( c . x - 1 , c . y , color )
map_image . set_pixel ( c . x , c . y - 1 , color )
map_image . set_pixel ( c . x , c . y , color )
## Создаёт новую отметку из координат экрана
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 _on_zoom_changed ( zoom : int ) :
if map_tile_stack . size ( ) != 0 :
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
map_zoom = zoom
var tile : = mercator . create_tile ( lnglat [ 0 ] , lnglat [ 1 ] , zoom )
map_tile_stack . clear ( )
map_tile_stack . append ( tile )
prev_tiles_dic . clear ( )
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 img_tile_to_map_image ( image : Image , dst_pos : Vector2i ) :
image . adjust_bcs ( map_bcs . x , map_bcs . y , map_bcs . z )
map_image . blit_rect ( image , map_tile , dst_pos )
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 _process ( _delta : float ) :
if ( map_tile_state == TASK_STATE . DONE ) and map_tile_stack . size ( ) :
var tile : mercator . Tile = map_tile_stack [ - 1 ]
var tile_addr : = map_tile_format % [ map_server_addr , tile . z , tile . x , tile . y ]
var rc = map_request . request ( tile_addr )
if rc == OK :
map_tile_state = TASK_STATE . WAIT
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 ( )
## Возвращает 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 , MAX_ZOOM )
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 , MIN_ZOOM )
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 :
log . error ( ' %s : Невозможно сформировать запрос HTTP ' % self )
map_tile_state = TASK_STATE . ERROR
elif map_tile_state == TASK_STATE . CHECK :
var root_tile : = mercator . Tile . new ( root_rect . size . x , root_rect . size . y , map_zoom )
var neighbors_tiles = mercator . get_perimetr_tiles ( root_tile , ( map_image . get_size ( ) . x / map_tile . size . x ) / 2 )
var neighbors_tiles_dic = { }
for tile in neighbors_tiles :
neighbors_tiles_dic [ [ tile . x , tile . y ] ] = false
if neighbors_tiles_dic != prev_tiles_dic :
for tile in neighbors_tiles :
tile . is_root = false
map_tile_stack . push_front ( tile )
prev_tiles_dic [ [ tile . x , tile . y ] ] = false
else :
if map_tile_stack . size ( ) == 0 :
signaller . emit_signal ( ' zoom_complete ' , map_zoom )
map_tile_state = TASK_STATE . DONE
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 _map_request_completed ( result , response_code , headers , data ) :
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 :
log . error ( ' %s : Запрос не обработан. result: \" %s \" , response_code: \" %s \" , headers: \" %s \" ' % [ self , result , response_code , headers ] )
map_tile_state = TASK_STATE . ERROR
if map_server_available :
map_server_available = false
emit_signal ( ' map_server_offline ' )
_queue [ tile . i ] = tile
return
var img_tile = Image . new ( )
var error = img_tile . load_png_from_buffer ( data )
if error != OK :
log . error ( ' %s : Полученые данные невозможно использовать для формироания изображения. result: \" %s \" , response_code: \" %s \" , headers: \" %s \" ' % [ self , result , response_code , headers ] )
map_tile_stat e = TASK_STATE . ERROR
if not map_server_available :
map_server_available = true
emit_signal ( ' map_server_online ' )
if cod e == 404 :
push_error ( ' ошибка: на сервере нет \" %s \" ' % tile . url )
return
var tile = map_tile_stack . pop_back ( )
map_tile_state = TASK_STATE . CHECK
if Error . OK ! = i mage_load_proc . call ( body ) :
push_error ( ' ошибка: не удалось получить изображение (неверный формат?) из \" %s \" ' % tile . url )
return
if tile . is_root :
# Отображение корневой плитки
map_image . f ill ( Color ( 0 , 0 , 0 , 0 ) )
var bounds : = mercator . bounds ( tile )
var lng_c = ( bounds . east + bounds . west ) / 2.0
var lat_c = ( bounds . north + bounds . south ) / 2.0
var sx : float = float ( map_tile . size . x ) / ( bounds . east - bounds . west ) * ( lng_c - tile . lng ) + map_tile . size . x / 2.0 # Cме ще ние в пискселях центра плитки от заданной точки по горизонтали.
var sy : float = float ( map_tile . size . y ) / ( bounds . north - bounds . south ) * ( lat_c - tile . lat ) - map_tile . size . y / 2.0 # Cме ще ние в пискселях центра плитки от заданной точки по вертикали.
var dst_pos : Vector2i = Vector2i ( int ( sx ) , int ( - sy ) ) + map_tile . size * map_mode
img_tile_to_map_image ( img_tile , dst_pos )
root_rect = Rect2i ( dst_pos . x , dst_pos . y , tile . x , tile . y )
# create texture from image and add it to the cache
tile . texture = ImageTexture . create_from_image ( image )
_cache [ t ile . 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 :
# Отображение соседних плиток
var delta : = Vector2i ( tile . x - root_rect . size . x , tile . y - root_rect . size . y )
var dst_pos : Vector2i = root_rect . position + delta * map_tile . size
img_tile_to_map_image ( img_tile , dst_pos )
mark_image_center ( map_image )
texture = ImageTexture . create_from_image ( map_image )
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_l = 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 :
spr_mask . visible = val
queue_redraw ( )