В процессе. Добавлена работа с картами Меркатора. Добавлен общий шейдер.
This commit is contained in:
@@ -19,6 +19,8 @@ boot_splash/show_image=false
|
|||||||
boot_splash/fullsize=false
|
boot_splash/fullsize=false
|
||||||
boot_splash/use_filter=false
|
boot_splash/use_filter=false
|
||||||
config/icon="res://data/icon.png"
|
config/icon="res://data/icon.png"
|
||||||
|
config/map_addr="https://a.tile.openstreetmap.org"
|
||||||
|
config/map_bcs=Vector3(0.5, 1.8, 0.3)
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ yemsboards="*res://scripts/yemsboards.gd"
|
|||||||
yemsconsts="*res://scripts/yems-consts.gd"
|
yemsconsts="*res://scripts/yems-consts.gd"
|
||||||
interfer="*res://scripts/interfer.gd"
|
interfer="*res://scripts/interfer.gd"
|
||||||
hotkeys="*res://scripts/hotkeys.gd"
|
hotkeys="*res://scripts/hotkeys.gd"
|
||||||
|
mercantile="*res://scripts/mercator.gd"
|
||||||
|
|
||||||
[debug]
|
[debug]
|
||||||
|
|
||||||
|
|||||||
62
scenes/tilemap.gd
Normal file
62
scenes/tilemap.gd
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
extends Sprite2D
|
||||||
|
|
||||||
|
## Состояния КА карты
|
||||||
|
enum TILE_STATE {
|
||||||
|
IDLE, ## Покой
|
||||||
|
WAIT, ## Ожидание
|
||||||
|
DONE, ## Выполнено
|
||||||
|
ERROR ## Ошибка
|
||||||
|
}
|
||||||
|
|
||||||
|
var curent_map_tile: Vector3i ## Текущая запрошенная плитка
|
||||||
|
var curent_state: TILE_STATE = TILE_STATE.IDLE ## Текущее состояние КА карты
|
||||||
|
var image: Image ## Изображение с картой
|
||||||
|
var map_bcs: Vector3 ## Яркость, контраст насыщенность
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# Create an HTTP request node and connect its completion signal.
|
||||||
|
var tile = mercantile.Tile.new()
|
||||||
|
print_debug('Плитка: %s %s' % [tile, tile.is_valid()])
|
||||||
|
var http_request = HTTPRequest.new()
|
||||||
|
add_child(http_request)
|
||||||
|
http_request.request_completed.connect(self._http_request_completed)
|
||||||
|
curent_map_tile = Vector3i(0, 0, 0)
|
||||||
|
|
||||||
|
image = Image.create(512, 512, true, Image.FORMAT_RGB8)
|
||||||
|
map_bcs = ProjectSettings.get_setting('application/config/map_bcs', Vector3(1.0, 1.0, 1.0))
|
||||||
|
|
||||||
|
var error = http_request.request('https://a.tile.openstreetmap.org/0/0/0.png')
|
||||||
|
if error == OK:
|
||||||
|
curent_state = TILE_STATE.WAIT
|
||||||
|
else:
|
||||||
|
push_error('Ошибка: Не возможно сформировать запрос HTTP')
|
||||||
|
curent_state = TILE_STATE.ERROR
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _http_request_completed(result, response_code, headers, data):
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
push_error('Ошибка: запрос не обработан: %s' % result)
|
||||||
|
curent_state = TILE_STATE.ERROR
|
||||||
|
return
|
||||||
|
|
||||||
|
var image0 = Image.new()
|
||||||
|
var error = image0.load_png_from_buffer(data)
|
||||||
|
if error != OK:
|
||||||
|
push_error('Ошибка: Полученые данные невозможно использовать для формироания изображения')
|
||||||
|
curent_state = TILE_STATE.ERROR
|
||||||
|
return
|
||||||
|
|
||||||
|
curent_state = TILE_STATE.DONE
|
||||||
|
var tile_size = image0.get_size()
|
||||||
|
|
||||||
|
image.blit_rect(image0, Rect2i(0, 0, tile_size.x, tile_size.y), Vector2i( 0, 0))
|
||||||
|
image.blit_rect(image0, Rect2i(0, 0, tile_size.x, tile_size.y), Vector2i(tile_size.x, 0))
|
||||||
|
image.blit_rect(image0, Rect2i(0, 0, tile_size.x, tile_size.y), Vector2i( 0, tile_size.y))
|
||||||
|
image.blit_rect(image0, Rect2i(0, 0, tile_size.x, tile_size.y), Vector2i(tile_size.y, tile_size.y))
|
||||||
|
image.adjust_bcs(map_bcs.x, map_bcs.y, map_bcs.z)
|
||||||
|
texture = ImageTexture.create_from_image(image)
|
||||||
15
scenes/tilemap.tscn
Normal file
15
scenes/tilemap.tscn
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://bbq6nklh36yij"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/tilemap.gd" id="1_mkfgj"]
|
||||||
|
[ext_resource type="Shader" path="res://shaders/tilemap.gdshader" id="1_tsdui"]
|
||||||
|
|
||||||
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_pmqvj"]
|
||||||
|
resource_local_to_scene = true
|
||||||
|
shader = ExtResource("1_tsdui")
|
||||||
|
shader_parameter/center = Vector2(0.5, 0.5)
|
||||||
|
shader_parameter/radius = 0.4
|
||||||
|
|
||||||
|
[node name="tilemap" type="Sprite2D"]
|
||||||
|
material = SubResource("ShaderMaterial_pmqvj")
|
||||||
|
position = Vector2(560, 559)
|
||||||
|
script = ExtResource("1_mkfgj")
|
||||||
@@ -3,13 +3,7 @@ extends Panel
|
|||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
# Called when the node enters the scene tree for the first time.
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if OS.get_cmdline_args().has('++ограниченный'):
|
if not OS.get_cmdline_args().has('++ограниченный'): return
|
||||||
$Label2.text = 'Текущий режим доступа: ограниченный'
|
$Label2.text = 'Текущий режим доступа: ограниченный'
|
||||||
for node in $GridContainer.get_children():
|
for node in $GridContainer.get_children().filter(func(b): return b is Button):
|
||||||
if node is Button:
|
node.disabled = true
|
||||||
node.disabled = true
|
|
||||||
|
|
||||||
|
|
||||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
|
||||||
func _process(delta: float) -> void:
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -238,3 +238,20 @@ offset_top = 27.0
|
|||||||
offset_right = 445.0
|
offset_right = 445.0
|
||||||
offset_bottom = 49.0
|
offset_bottom = 49.0
|
||||||
text = "Текущий режим доступа: полный"
|
text = "Текущий режим доступа: полный"
|
||||||
|
|
||||||
|
[node name="edt_mapaddr" type="LineEdit" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 536.0
|
||||||
|
offset_top = 60.0
|
||||||
|
offset_right = 908.0
|
||||||
|
offset_bottom = 87.0
|
||||||
|
text = "https://a.tile.openstreetmap.org"
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="edt_mapaddr"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_top = -30.0
|
||||||
|
offset_right = 372.0
|
||||||
|
offset_bottom = -8.0
|
||||||
|
text = "Адрес сервера карт"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ func on_threats_resized(threats: Dictionary): $count_all_pad/count_all.text = '%
|
|||||||
|
|
||||||
## Производит инициализацию
|
## Производит инициализацию
|
||||||
func _ready():
|
func _ready():
|
||||||
|
$tilemap.position = $canvas.get_strob_center()
|
||||||
$btn_strob.button_connect('pressed', Callable(self, 'on_button_strobe_pressed'))
|
$btn_strob.button_connect('pressed', Callable(self, 'on_button_strobe_pressed'))
|
||||||
$btn_view.button_connect('toggled', Callable(self, 'on_button_view_toggled'))
|
$btn_view.button_connect('toggled', Callable(self, 'on_button_view_toggled'))
|
||||||
$grd_btns/btn_off.set_pressed(true)
|
$grd_btns/btn_off.set_pressed(true)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=10 format=3 uid="uid://b276iygic5itk"]
|
[gd_scene load_steps=13 format=3 uid="uid://b276iygic5itk"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scenes/работа.gd" id="1_vmjjl"]
|
[ext_resource type="Script" path="res://scenes/работа.gd" id="1_vmjjl"]
|
||||||
[ext_resource type="PackedScene" uid="uid://nl1vklubr5kr" path="res://scenes/bip.tscn" id="2_ew6j4"]
|
[ext_resource type="PackedScene" uid="uid://nl1vklubr5kr" path="res://scenes/bip.tscn" id="2_ew6j4"]
|
||||||
@@ -9,6 +9,14 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://b5kjdyxuwsot5" path="res://scenes/canvas.tscn" id="7_pvt8f"]
|
[ext_resource type="PackedScene" uid="uid://b5kjdyxuwsot5" path="res://scenes/canvas.tscn" id="7_pvt8f"]
|
||||||
[ext_resource type="PackedScene" uid="uid://da7w3vkhadfwe" path="res://scenes/button-flat.tscn" id="8_r2k1p"]
|
[ext_resource type="PackedScene" uid="uid://da7w3vkhadfwe" path="res://scenes/button-flat.tscn" id="8_r2k1p"]
|
||||||
[ext_resource type="PackedScene" uid="uid://0uqi1ghf7sma" path="res://scenes/btn_scale.tscn" id="8_xddop"]
|
[ext_resource type="PackedScene" uid="uid://0uqi1ghf7sma" path="res://scenes/btn_scale.tscn" id="8_xddop"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bbq6nklh36yij" path="res://scenes/tilemap.tscn" id="10_wpi4s"]
|
||||||
|
[ext_resource type="Shader" path="res://shaders/tilemap.gdshader" id="11_7ohqv"]
|
||||||
|
|
||||||
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_x7kiv"]
|
||||||
|
resource_local_to_scene = true
|
||||||
|
shader = ExtResource("11_7ohqv")
|
||||||
|
shader_parameter/center = Vector2(0.5, 0.5)
|
||||||
|
shader_parameter/radius = 0.4
|
||||||
|
|
||||||
[node name="Работа" type="Panel"]
|
[node name="Работа" type="Panel"]
|
||||||
script = ExtResource("1_vmjjl")
|
script = ExtResource("1_vmjjl")
|
||||||
@@ -18,10 +26,10 @@ col_red = Color(1, 0.568627, 0.431373, 0.239216)
|
|||||||
drag_scale_band = 0.05
|
drag_scale_band = 0.05
|
||||||
drag_scale_width = 0.5
|
drag_scale_width = 0.5
|
||||||
freq_high = 6000.0
|
freq_high = 6000.0
|
||||||
freq_low = 200.0
|
freq_low = 400.0
|
||||||
radius_center = Vector2(560, 560)
|
radius_center = Vector2(560, 560)
|
||||||
radius_internal = 200.0
|
radius_internal = 200.0
|
||||||
radius_outter = 550.0
|
radius_outter = 400.0
|
||||||
strob_max_band = 15.538
|
strob_max_band = 15.538
|
||||||
strob_max_width = 90.0
|
strob_max_width = 90.0
|
||||||
strob_min_band = 0.1
|
strob_min_band = 0.1
|
||||||
@@ -35,6 +43,7 @@ offset_left = 1114.0
|
|||||||
offset_top = 7.0
|
offset_top = 7.0
|
||||||
offset_right = 1594.0
|
offset_right = 1594.0
|
||||||
offset_bottom = 1170.0
|
offset_bottom = 1170.0
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
[node name="canvas" parent="." instance=ExtResource("7_pvt8f")]
|
[node name="canvas" parent="." instance=ExtResource("7_pvt8f")]
|
||||||
metadata/_edit_lock_ = true
|
metadata/_edit_lock_ = true
|
||||||
@@ -45,6 +54,7 @@ offset_left = 10.0
|
|||||||
offset_top = 1116.0
|
offset_top = 1116.0
|
||||||
offset_right = 1106.0
|
offset_right = 1106.0
|
||||||
offset_bottom = 1170.0
|
offset_bottom = 1170.0
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
[node name="count_all_pad" parent="." instance=ExtResource("6_l7wyl")]
|
[node name="count_all_pad" parent="." instance=ExtResource("6_l7wyl")]
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
@@ -123,6 +133,10 @@ offset_top = 1038.0
|
|||||||
offset_right = 1104.0
|
offset_right = 1104.0
|
||||||
offset_bottom = 1056.0
|
offset_bottom = 1056.0
|
||||||
|
|
||||||
|
[node name="tilemap" parent="." instance=ExtResource("10_wpi4s")]
|
||||||
|
material = SubResource("ShaderMaterial_x7kiv")
|
||||||
|
position = Vector2(548, 552)
|
||||||
|
|
||||||
[connection signal="drag_begin" from="." to="." method="_on_drag_begin"]
|
[connection signal="drag_begin" from="." to="." method="_on_drag_begin"]
|
||||||
[connection signal="drag_continue" from="." to="." method="_on_drag_continue"]
|
[connection signal="drag_continue" from="." to="." method="_on_drag_continue"]
|
||||||
[connection signal="pressed" from="btn_scale" to="." method="_on_btn_scale_pressed"]
|
[connection signal="pressed" from="btn_scale" to="." method="_on_btn_scale_pressed"]
|
||||||
|
|||||||
664
scripts/mercator.gd
Normal file
664
scripts/mercator.gd
Normal file
@@ -0,0 +1,664 @@
|
|||||||
|
extends Node
|
||||||
|
## Web mercator XYZ tile utilities
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
## An XYZ web mercator tile
|
||||||
|
## x, y, z : int
|
||||||
|
## x and y indexes of the tile and zoom level z.
|
||||||
|
class Tile:
|
||||||
|
var x: int
|
||||||
|
var y: int
|
||||||
|
var z: int
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.x = x0
|
||||||
|
self.y = y0
|
||||||
|
self.z = z0
|
||||||
|
|
||||||
|
func is_valid() -> bool:
|
||||||
|
if (self.z >= 0) and (self.z < MAX_ZOOM):
|
||||||
|
var mz: int = 2 ** self.z
|
||||||
|
return (self.x >= 0) and (self.x < mz) \
|
||||||
|
and (self.y >= 0) and (self.y < mz)
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
## Пара долготы и широты.
|
||||||
|
class LngLat:
|
||||||
|
var lng: float = 0.0 ## Долгота в десятичных градусах восточной долготы.
|
||||||
|
var lat: float = 0.0 ## Широта в десятичных градусах северной широты.
|
||||||
|
func _init(lng0, lat0):
|
||||||
|
self.lng = lng0
|
||||||
|
self.lat = lat0
|
||||||
|
|
||||||
|
|
||||||
|
## Географическое ограничивающее поле.
|
||||||
|
## запад, юг, восток, север : float
|
||||||
|
## Значения границ в десятичных градусах.
|
||||||
|
class LngLatBbox:
|
||||||
|
var west: float
|
||||||
|
var south: float
|
||||||
|
var east: float
|
||||||
|
var north: float
|
||||||
|
func as_array(): return [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
|
||||||
|
self.east = e
|
||||||
|
self.north = n
|
||||||
|
|
||||||
|
|
||||||
|
## Граничная область веб-меркатора.
|
||||||
|
## Атрибуты: left, bottom, right, top : float.
|
||||||
|
## Значения границ в метрах.
|
||||||
|
class Bbox:
|
||||||
|
var left: float
|
||||||
|
var bottom: float
|
||||||
|
var right: float
|
||||||
|
var top: float
|
||||||
|
func as_array(): return [self.left, self.bottom, self.right, self.top]
|
||||||
|
func _init(l: float = 0.0, b: float = 0.0, r: float = 0.0, t: float = 0.0):
|
||||||
|
self.left = l
|
||||||
|
self.bottom = b
|
||||||
|
self.right = r
|
||||||
|
self.top = t
|
||||||
|
|
||||||
|
|
||||||
|
func degrees(v: float): return (v * 180.0) / PI
|
||||||
|
|
||||||
|
|
||||||
|
func radians(v: float): return (v * PI) / 180.0
|
||||||
|
|
||||||
|
|
||||||
|
func rshift(val: int, n: int): return (val % 0x100000000) >> n
|
||||||
|
|
||||||
|
|
||||||
|
## Возвращает плитки и занимаемый ими размер области в пикселях:
|
||||||
|
## ширина, высота, левая граница, верхняя граница, плитки
|
||||||
|
## [param west] [float] Западная граница области
|
||||||
|
## [param east] [float] Восточная граница области
|
||||||
|
## [param south] [float] Южная граница области
|
||||||
|
## [param north] [float] Северная граница области
|
||||||
|
## [param size] [float] Размер плитки в пикселях
|
||||||
|
## [param zoom] [float] Детализация
|
||||||
|
func get_tiles(west, south, east, north, zoom, size):
|
||||||
|
var tiles = [create_tiles(west, south, east, north, zoom)]
|
||||||
|
var mm = []
|
||||||
|
for t in tiles: mm.append(t.x)
|
||||||
|
var min_x = mm.min()
|
||||||
|
var max_x = mm.max()
|
||||||
|
|
||||||
|
mm.clear()
|
||||||
|
for t in tiles: mm.append(t.y)
|
||||||
|
var min_y = mm.min()
|
||||||
|
var max_y = mm.max()
|
||||||
|
|
||||||
|
var w = size * (max_x - min_x + 1)
|
||||||
|
var h = size * (max_y - min_y + 1)
|
||||||
|
return [w, h, min_x, min_y, tiles, max_x - min_x + 1]
|
||||||
|
|
||||||
|
|
||||||
|
## Возвращает размер области в метрах
|
||||||
|
## [param west] [float] - Западная граница области
|
||||||
|
## [param param] [float] - Восточная граница области
|
||||||
|
## [param south] [float] - Южная граница области
|
||||||
|
## [param north] [float] - Северная граница области
|
||||||
|
## [return] [Array]
|
||||||
|
func get_size_from_degrees(west, south, east, north) -> Array[float]:
|
||||||
|
var xy0: = xy(west, north, true)
|
||||||
|
var xy1: = xy(east, south, true)
|
||||||
|
return [xy0[0] - xy1[0], xy0[1] - xy1[1]]
|
||||||
|
|
||||||
|
|
||||||
|
## Возвращает границы области в градусах
|
||||||
|
## [param m] [float] - Размер карты в метрах.
|
||||||
|
## [param lng] [float] - Долгота центра области в градусах (-180...180).
|
||||||
|
## [param lat] [float] - Широта центра области в градусах (-90...90).
|
||||||
|
## [return] [float, float, float, float]
|
||||||
|
func get_degrees_bounds(m: float, lng: float, lat: float):
|
||||||
|
m /= 2.0
|
||||||
|
var x_y = xy(lng, lat, true)
|
||||||
|
var ws = lnglat(x_y[0] - m, x_y[1] - m)
|
||||||
|
var en = lnglat(x_y[0] + m, x_y[1] + m)
|
||||||
|
return [ws[0], ws[1], en[0], en[1]]
|
||||||
|
|
||||||
|
|
||||||
|
func get_tiles_bounds(tiles):
|
||||||
|
var mm = []
|
||||||
|
for t in tiles: mm.append(bounds(t).west)
|
||||||
|
var mmw = mm.min(); mm.clear()
|
||||||
|
|
||||||
|
for t in tiles: mm.append(bounds(t).south)
|
||||||
|
var mms = mm.min(); mm.clear()
|
||||||
|
|
||||||
|
for t in tiles: mm.append(bounds(t).east)
|
||||||
|
var mme = mm.max(); mm.clear()
|
||||||
|
|
||||||
|
for t in tiles: mm.append(bounds(t).north)
|
||||||
|
var mmn = mm.max(); mm.clear()
|
||||||
|
|
||||||
|
return [mmw, mms, mme, mmn]
|
||||||
|
|
||||||
|
|
||||||
|
## Возвращает левую верхнюю долготу и широту плитки.
|
||||||
|
## [param tile] Плитка или последовательность int. Может быть либо экземпляр
|
||||||
|
## Tile, либо 3 ints, X, Y, Z.
|
||||||
|
## [return] LngLat
|
||||||
|
## [codeblock]
|
||||||
|
## >>> ul(Tile(x=0, y=0, z=1))
|
||||||
|
## LngLat(lng=-180.0, lat=85.0511287798066)
|
||||||
|
## >>> mercantile.ul(1, 1, 1)
|
||||||
|
## LngLat(lng=0.0, lat=0.0)
|
||||||
|
## [/codeblock]
|
||||||
|
func ul(tile: Tile, zoom: int):
|
||||||
|
var Z2 = pow(2, zoom)
|
||||||
|
var lon_deg = tile.x / Z2 * 360.0 - 180.0
|
||||||
|
var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / Z2)))
|
||||||
|
var lat_deg = degrees(lat_rad)
|
||||||
|
return LngLat.new(lon_deg, lat_deg)
|
||||||
|
|
||||||
|
|
||||||
|
## Возвращает левую верхнюю долготу и широту плитки.
|
||||||
|
## [param tile] [Плитка] Может быть либо экземпляром Tile, либо 3 интами, X, Y, Z.
|
||||||
|
## [return] [LngLat]
|
||||||
|
## [codeblock]
|
||||||
|
## >>> ul(Tile(x=0, y=0, z=1))
|
||||||
|
## LngLat(lng=-180.0, lat=85.0511287798066)
|
||||||
|
## >>> mercantile.ul(1, 1, 1)
|
||||||
|
## LngLat(lng=0.0, lat=0.0)
|
||||||
|
## [/codeblock]
|
||||||
|
func ul2(tile: Tile) -> Array[float]:
|
||||||
|
var z2 = pow(2, tile.z)
|
||||||
|
var lon_deg = tile.x / z2 * 360.0 - 180.0
|
||||||
|
var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / z2)))
|
||||||
|
var lat_deg = degrees(lat_rad)
|
||||||
|
return [lat_deg, lon_deg]
|
||||||
|
|
||||||
|
|
||||||
|
## Возвращает граничную область плитки.
|
||||||
|
## [param tile] [Tile] или [Array]. Может быть либо экземпляром [Tile], либо 3
|
||||||
|
## целыми числами (X, Y, Z).
|
||||||
|
## [return] [LngLatBbox]
|
||||||
|
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 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_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:
|
||||||
|
if lng > 180.0: lng = 180.0
|
||||||
|
elif lng < -180.0: lng = -180.0
|
||||||
|
if lat > 90.0: lat = 90.0
|
||||||
|
elif lat < -90.0: lat = -90.0
|
||||||
|
return [lng, lat]
|
||||||
|
|
||||||
|
|
||||||
|
## Преобразование долготы и широты в веб-меркатор x, y.
|
||||||
|
## [param lng] [param lat]: float
|
||||||
|
## Долгота и широта в десятичных градусах [param truncate] [bool] -Нужно ли
|
||||||
|
## усекать или обрезать входные данные до пределов веб-меркатора.
|
||||||
|
## [return] [x: float, y: float]. [param y] будет [b]inf[/b] на северном полюсе.
|
||||||
|
## ([param lat] >= 90) и [b]-inf[/b] на южном полюсе ([param lat] <= -90).
|
||||||
|
func xy(lng: float, lat: float, truncate: bool = false) -> Array[float]:
|
||||||
|
var ll = truncate_lnglat(lng, lat) if truncate else [0.0, 0.0]
|
||||||
|
var x: float = RE * radians(lng)
|
||||||
|
var y: float = 0.0
|
||||||
|
if ll[1] <= -90.0: y = float('-inf')
|
||||||
|
elif ll[1] >= 90.0: y = float('inf')
|
||||||
|
else: y = RE * log(tan((PI * 0.25) + (0.5 * radians(lat))))
|
||||||
|
return [x, y]
|
||||||
|
|
||||||
|
|
||||||
|
## Преобразование координат веб-меркатора x, y в долготу и широту.
|
||||||
|
## [param x] [float], [param y] [float] - Координаты веб-меркатора в метрах.
|
||||||
|
## [param truncate] [bool] - Нужно ли усекать или обрезать входные данные до границ веб-меркатора.
|
||||||
|
## [return] [LngLat]
|
||||||
|
func lnglat(x, y, truncate=false):
|
||||||
|
var lng = x * R2D / RE
|
||||||
|
var lat = ((PI * 0.5) - 2.0 * atan(exp(-y / RE))) * R2D
|
||||||
|
var ll = truncate_lnglat(lng, lat) if truncate else [0.0, 0.0]
|
||||||
|
return LngLat.new(ll[0], ll[1])
|
||||||
|
|
||||||
|
|
||||||
|
## Соседи плитки. [func neighbors] возвращает до восьми
|
||||||
|
## соседних плиток, при этом плитки будут опущены, если они не являются действительными, например.
|
||||||
|
## Tile(-1, -1, z). [param tile]: Плитка или последовательность int
|
||||||
|
## Может быть либо экземпляр Tile, либо 3 ints, X, Y, Z.
|
||||||
|
## [return] Массив
|
||||||
|
## [codeblock]
|
||||||
|
## >>> 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]
|
||||||
|
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
|
||||||
|
var t = Tile.new(tile.x + i, tile.y + j, tile.z)
|
||||||
|
if t.is_valid():
|
||||||
|
tiles.append(t)
|
||||||
|
return tiles
|
||||||
|
|
||||||
|
|
||||||
|
## Получение меркаторской граничной рамки веб-плитки
|
||||||
|
## [param tile] - Плитка или последовательность int. Может быть либо экземпляр
|
||||||
|
## [Tile], либо 3 ints, X, Y, Z.
|
||||||
|
## [return] [Bbox]
|
||||||
|
## Примечания. Эпсилон вычитается из правой границы и прибавляется к нижней.
|
||||||
|
func xy_bounds(tile: Tile, zoom: int) -> Bbox:
|
||||||
|
var tile_size = CE / pow(2, zoom)
|
||||||
|
var left = tile.x * tile_size - CE / 2
|
||||||
|
var right = left + tile_size
|
||||||
|
var top = CE / 2 - tile.y * tile_size
|
||||||
|
var bottom = top - tile_size
|
||||||
|
return Bbox.new(left, bottom, right, top)
|
||||||
|
|
||||||
|
|
||||||
|
func _xy(lng, lat, truncate=false) -> Array:
|
||||||
|
var ll: = [lng, lat] if not truncate else truncate_lnglat(lng, lat)
|
||||||
|
var x = 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]
|
||||||
|
|
||||||
|
|
||||||
|
## Получение плитки, содержащей долготу и широту
|
||||||
|
## [param lng], [param lat] [float] - Пара долготы и широты в десятичных градусах.
|
||||||
|
## [param zoom] [int] - Уровень масштабирования веб-меркатора.
|
||||||
|
## [param truncate] [bool] - Нужно ли усекать вводимые данные до границ веб-меркатора.
|
||||||
|
## [return] [Tile]
|
||||||
|
func create_tile(lng: float, lat: float, zoom: int, truncate: bool=false) -> Tile:
|
||||||
|
var xy0: = _xy(lng, lat, truncate)
|
||||||
|
var Z2 = pow(2, zoom)
|
||||||
|
|
||||||
|
# Чтобы устранить потерю точности при переходе от одной плитки к другой
|
||||||
|
# и 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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
tile.z = zoom
|
||||||
|
return tile
|
||||||
|
|
||||||
|
|
||||||
|
## Получение четверного ключа плитки
|
||||||
|
## [param tile] [Tile] или последовательность [int]. Может быть либо экземпляр
|
||||||
|
## [Tile] или 3 целых, X, Y, Z.
|
||||||
|
## [return] [String]
|
||||||
|
func quadkey(tile: Tile, zoom) -> String:
|
||||||
|
var rs: = String()
|
||||||
|
for z in range(zoom, 0, -1):
|
||||||
|
var digit = 0
|
||||||
|
var mask = 1 << (z - 1)
|
||||||
|
if tile.x & mask:
|
||||||
|
digit += 1
|
||||||
|
if tile.y & mask:
|
||||||
|
digit += 2
|
||||||
|
rs += str(digit)
|
||||||
|
return rs
|
||||||
|
|
||||||
|
|
||||||
|
## Получение плитки, соответствующей четверному ключу.
|
||||||
|
## [param qk] [String] - Строка четверного ключа.
|
||||||
|
## [return] [Tile]
|
||||||
|
func quadkey_to_tile(qk: Array):
|
||||||
|
var tile: = Tile.new()
|
||||||
|
if len(qk) == 0: return tile
|
||||||
|
var i = 0
|
||||||
|
qk.reverse()
|
||||||
|
for digit in qk:
|
||||||
|
var mask = 1 << i
|
||||||
|
i += 1
|
||||||
|
if digit == '1':
|
||||||
|
tile.x |= mask
|
||||||
|
elif digit == '2':
|
||||||
|
tile.y |= mask
|
||||||
|
elif digit == '3':
|
||||||
|
tile.x |= mask
|
||||||
|
tile.y |= mask
|
||||||
|
elif digit == '0':
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
push_error('Не ожидаемый четырёхзначный знак: \"%s\"' % digit)
|
||||||
|
tile.z = i
|
||||||
|
return tile
|
||||||
|
|
||||||
|
|
||||||
|
## Получение плиток, перекрываемых географической рамкой
|
||||||
|
## [param west], [param south], [param east], [param north]: последовательность [float]
|
||||||
|
## - Значения границ в десятичных градусах.
|
||||||
|
## [param zooms] [int] или [Array[int]]. - Один или несколько уровней масштабирования.
|
||||||
|
## [param truncate] [bool] - Нужно ли усекать вводимые данные до границ веб-меркатора.
|
||||||
|
## [return] Tile
|
||||||
|
## Примечания. В параметрах south и east используется небольшой эпсилон, чтобы
|
||||||
|
## функция выдавала ровно одну плитку при задании границ этой же плитки.
|
||||||
|
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)
|
||||||
|
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):
|
||||||
|
zooms = [zooms]
|
||||||
|
for box in bboxes:
|
||||||
|
var w = box[0]
|
||||||
|
var s = box[1]
|
||||||
|
var e = box[2]
|
||||||
|
var n = box[3]
|
||||||
|
# Зажим граничных значений.
|
||||||
|
w = max(-180.0, w)
|
||||||
|
s = max(-85.051129, s)
|
||||||
|
e = min(180.0, e)
|
||||||
|
n = min(85.051129, n)
|
||||||
|
for z in zooms:
|
||||||
|
var ul_tile = create_tile(w, n, z)
|
||||||
|
var lr_tile = create_tile(e - LL_EPSILON, s + LL_EPSILON, z)
|
||||||
|
for i in range(ul_tile.x, lr_tile.x + 1):
|
||||||
|
for j in range(ul_tile.y, lr_tile.y + 1):
|
||||||
|
tiles.append(Tile.new(i, j, z))
|
||||||
|
return tiles
|
||||||
|
|
||||||
|
|
||||||
|
## Получение родителя плитки. Родительская плитка - это плитка на один уровень масштабирования ниже, которая
|
||||||
|
## содержит данную "дочернюю" плитку.
|
||||||
|
## [param tile] [Tile] или последовательность int - Может быть либо экземпляром Tile.
|
||||||
|
## или 3 ints, X, Y, Z.
|
||||||
|
## [param zoom] [int] - Определяет уровень *масштабирования* возвращаемой родительской плитки.
|
||||||
|
## По умолчанию это значение на один ниже, чем у плитки (непосредственного родителя).
|
||||||
|
## [return] [Tile]
|
||||||
|
## [codeblock]
|
||||||
|
## >>> parent(Tile(0, 0, 2))
|
||||||
|
## Tile(x=0, y=0, z=1)
|
||||||
|
## >>> parent(Tile(0, 0, 2), zoom=0)
|
||||||
|
## Tile(x=0, y=0, z=0)
|
||||||
|
## [/codeblock]
|
||||||
|
func parent(tile: Tile, zoom=null):
|
||||||
|
if tile.z == 0: return null
|
||||||
|
|
||||||
|
assert((zoom != null) and (tile.z > zoom or zoom == int(zoom)), 'Масштаб (zoom) должен быть целым числом и меньше масштаба входной плитки (tile).')
|
||||||
|
assert(tile.x == int(tile.x) and tile.y == int(tile.y) and tile.z == int(tile.z), 'Родитель нецелочисленной плитки (tile) является неуточнённым')
|
||||||
|
|
||||||
|
var target_zoom = tile.z - 1 if zoom == null else zoom
|
||||||
|
|
||||||
|
# Алгоритм, в значительной степени вдохновленный https://github.com/mapbox/tilebelt
|
||||||
|
var return_tile = Tile.new(tile.x, tile.y, tile.z)
|
||||||
|
while return_tile.z > target_zoom:
|
||||||
|
tile.x = return_tile.x
|
||||||
|
tile.y = return_tile.y
|
||||||
|
tile.z = return_tile.z
|
||||||
|
if tile.x % 2 == 0 and tile.y % 2 == 0:
|
||||||
|
return_tile = Tile.new(tile.x / 2, tile.y / 2, tile.z - 1)
|
||||||
|
elif tile.x % 2 == 0:
|
||||||
|
return_tile = Tile.new(tile.x / 2, (tile.y - 1) / 2, tile.z - 1)
|
||||||
|
elif not tile.x % 2 == 0 and tile.y % 2 == 0:
|
||||||
|
return_tile = Tile.new((tile.x - 1) / 2, tile.y / 2, tile.z - 1)
|
||||||
|
else:
|
||||||
|
return_tile = Tile.new((tile.x - 1) / 2, (tile.y - 1) / 2, tile.z - 1)
|
||||||
|
return return_tile
|
||||||
|
|
||||||
|
|
||||||
|
## Получение дочерних элементов плитки. Дочерние элементы упорядочены:
|
||||||
|
## верхний-левый, верхний-правый, bottom-right, bottom-left.
|
||||||
|
## [tile] [Tile] или последовательность int. Может быть либо экземпляром Tile,
|
||||||
|
## либо 3 интами, X, Y, Z.
|
||||||
|
## [param zoom] [int] Возвращает все дочерние элементы с масштабом *zoom*, в порядке убывания глубины.
|
||||||
|
## в порядке намотки по часовой стрелке. Если не указано, возвращаются ближайшие (т.е. zoom + 1)
|
||||||
|
## дочерние элементы плитки.
|
||||||
|
## [return] [Array]
|
||||||
|
## [codeblock]
|
||||||
|
## >>> children(Tile(0, 0, 0))
|
||||||
|
## [Tile(x=0, y=0, z=1), Tile(x=0, y=1, z=1), Tile(x=1, y=0, z=1), Tile(x=1, y=1, z=1)]
|
||||||
|
## >>> 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)')
|
||||||
|
var target_zoom = zoom if zoom != null else tile.z + 1
|
||||||
|
var tiles: Array[Tile] = [tile]
|
||||||
|
while tiles[0].z >= target_zoom:
|
||||||
|
tile = tiles.pop_at(0)
|
||||||
|
tiles += [
|
||||||
|
Tile.new(tile.x * 2, tile.y * 2, tile.z + 1),
|
||||||
|
Tile.new(tile.x * 2 + 1, tile.y * 2, tile.z + 1),
|
||||||
|
Tile.new(tile.x * 2 + 1, tile.y * 2 + 1, tile.z + 1),
|
||||||
|
Tile.new(tile.x * 2, tile.y * 2 + 1, tile.z + 1) ]
|
||||||
|
return tiles
|
||||||
|
|
||||||
|
|
||||||
|
## Проверяется, есть ли в [param merge_set] 4 плитки, которые могут быть объединены.
|
||||||
|
## Если есть, то происходит их слияние. Возвращает список плиток, а также булево
|
||||||
|
## число, указывающее, были ли они объединены. При многократном применяя
|
||||||
|
## [method merge], можно упростить набор плиток.
|
||||||
|
func merge(merge_set):
|
||||||
|
var upwards_merge: = {}
|
||||||
|
for tile in merge_set.keys():
|
||||||
|
var tile_parent = parent(tile)
|
||||||
|
if not upwards_merge.has(tile_parent):
|
||||||
|
upwards_merge[tile_parent] = {}
|
||||||
|
upwards_merge[tile_parent].update({tile: false})
|
||||||
|
var current_tileset: = []
|
||||||
|
var changed: = false
|
||||||
|
for supertile in upwards_merge.keys():
|
||||||
|
var tile_children = upwards_merge[supertile]
|
||||||
|
if len(tile_children) == 4:
|
||||||
|
current_tileset += [supertile]
|
||||||
|
changed = true
|
||||||
|
else:
|
||||||
|
current_tileset += [tile_children]
|
||||||
|
return [current_tileset, changed]
|
||||||
|
|
||||||
|
|
||||||
|
## Максимально уменьшает размер набора плиток путем слияния листьев с родителями.
|
||||||
|
## [param tiles] - Последовательность плиток для слияния.
|
||||||
|
## [return] [Array]
|
||||||
|
func simplify(tiles: Array):
|
||||||
|
# Проверьте, существует ли уже плитка и ее родитель.
|
||||||
|
# Убедитесь, что плитки отсортированы по масштабу, поэтому родительские
|
||||||
|
# плитки встречаются первыми. Если да, отбросьте дочернюю плитку
|
||||||
|
# (она покрыта родительской).
|
||||||
|
var root_set = {}
|
||||||
|
tiles.sort_custom(func(a, b): return a[2] < b[2])
|
||||||
|
for tile in tiles:
|
||||||
|
var is_new_tile: bool = true
|
||||||
|
var supertiles = []
|
||||||
|
for i in tile.z:
|
||||||
|
supertiles.append(parent(tile, i))
|
||||||
|
for supertile in supertiles:
|
||||||
|
if supertile in root_set:
|
||||||
|
is_new_tile = false
|
||||||
|
continue
|
||||||
|
if is_new_tile:
|
||||||
|
root_set.update({tile: false})
|
||||||
|
|
||||||
|
# Повторять слияние до тех пор, пока дальнейшее упрощение перестанет быть невозможным.
|
||||||
|
var is_merging: bool = true
|
||||||
|
while is_merging:
|
||||||
|
var root_set_is_merging = merge(root_set)
|
||||||
|
root_set = root_set_is_merging[0]
|
||||||
|
is_merging = root_set_is_merging[1]
|
||||||
|
return root_set
|
||||||
|
|
||||||
|
|
||||||
|
## Получение наименьшей плитки, содержащей географическую границу.
|
||||||
|
## Если bbox охватывает линии lng 0 или lat 0, ограничивающая плитка будет иметь вид
|
||||||
|
## Tile(x=0, y=0, z=0).
|
||||||
|
## [param bbox] последовательность [float] значений границ запада, юга, востока,
|
||||||
|
## севера в десятичных градусах.
|
||||||
|
## [return] [Tile]
|
||||||
|
func bounding_tile(bbox, truncate: bool = false):
|
||||||
|
if len(bbox) == 2:
|
||||||
|
bbox += bbox
|
||||||
|
|
||||||
|
var w = bbox[0]
|
||||||
|
var s = bbox[1]
|
||||||
|
var e = bbox[2]
|
||||||
|
var n = bbox[3]
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
e -= LL_EPSILON
|
||||||
|
s += LL_EPSILON
|
||||||
|
|
||||||
|
var tmin = create_tile(w, n, 32)
|
||||||
|
var tmax = create_tile(e, s, 32)
|
||||||
|
assert(tmin.is_valid())
|
||||||
|
assert(tmax.is_valid())
|
||||||
|
|
||||||
|
var cell = [tmin.x, tmin.y, tmax.x, tmax.y]
|
||||||
|
var z = _getBboxZoom(cell)
|
||||||
|
|
||||||
|
if z == 0:
|
||||||
|
return Tile.new()
|
||||||
|
|
||||||
|
var x = rshift(cell[0], 32 - z)
|
||||||
|
var y = rshift(cell[1], 32 - z)
|
||||||
|
|
||||||
|
return Tile.new(x, y, z)
|
||||||
|
|
||||||
|
|
||||||
|
func _getBboxZoom(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):
|
||||||
|
return z
|
||||||
|
return MAX_ZOOM
|
||||||
|
|
||||||
|
|
||||||
|
## Получение GeoJSON-функции, соответствующей плитке
|
||||||
|
## [param tile] [Плитка] или последовательность int - Может быть либо экземпляром [Плитка], либо 3 int, X, Y, Z.
|
||||||
|
## [Tile] или 3 целых, X, Y, Z.
|
||||||
|
## [param fid] [String] - Идентификатор объекта.
|
||||||
|
## [param props] [Dictionary] - Необязательные дополнительные свойства признака.
|
||||||
|
## [param projected] [String] - Можно создать нестандартный веб-меркатор GeoJSON.
|
||||||
|
## передав 'mercator'.
|
||||||
|
## [param buffer] [float] - Необязательное буферное расстояние для полигона GeoJSON.
|
||||||
|
## [param precision] [int] - Координаты GeoJSON будут усечены до данного
|
||||||
|
## количества знаков после запятой.
|
||||||
|
## [return] [Dictionary]
|
||||||
|
func feature(tile: Tile, fid=null, props=null, projected='geographic', buffer=null):
|
||||||
|
var west_south_east_north = bounds(tile).as_array()
|
||||||
|
var west = west_south_east_north[0]
|
||||||
|
var south = west_south_east_north[1]
|
||||||
|
var east = west_south_east_north[2]
|
||||||
|
var north = west_south_east_north[3]
|
||||||
|
if projected == 'mercator':
|
||||||
|
var west_south = xy(west, south, false)
|
||||||
|
west = west_south[0]
|
||||||
|
south = west_south[1]
|
||||||
|
var east_north = xy(east, north, false)
|
||||||
|
east = east_north[0]
|
||||||
|
north = east_north[1]
|
||||||
|
|
||||||
|
if buffer:
|
||||||
|
west -= buffer
|
||||||
|
south -= buffer
|
||||||
|
east += buffer
|
||||||
|
north += buffer
|
||||||
|
|
||||||
|
west = round(west)
|
||||||
|
south = round(south)
|
||||||
|
east = round(east)
|
||||||
|
north = round(north)
|
||||||
|
|
||||||
|
var bbox: = [min(west, east), min(south, north), max(west, east), max(south, north)]
|
||||||
|
var geom: = {'type': 'Polygon', 'coordinates': [[[west, south], [west, north], [east, north], [east, south], [west, south]]] }
|
||||||
|
var xyz: = str(tile)
|
||||||
|
var feat: = {
|
||||||
|
'type': 'Feature',
|
||||||
|
'bbox': bbox,
|
||||||
|
'id': xyz,
|
||||||
|
'geometry': geom,
|
||||||
|
'properties': {'title': 'XYZ tile %s' % xyz} }
|
||||||
|
|
||||||
|
if props:
|
||||||
|
feat['properties'].update(props)
|
||||||
|
|
||||||
|
if fid != null:
|
||||||
|
feat['id'] = fid
|
||||||
|
|
||||||
|
return feat
|
||||||
|
|
||||||
|
|
||||||
|
func isinstance(obj, types: Array):
|
||||||
|
for type in types:
|
||||||
|
if is_instance_of(obj, type):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
## Все кортежи координат из геометрии, объекта или коллекции
|
||||||
|
## [param lng] [float] - Долгота
|
||||||
|
## [param lat] [float] - широта
|
||||||
|
## [return] [Array]
|
||||||
|
func _coords(obj) -> Array:
|
||||||
|
var coordinates = []
|
||||||
|
if is_instance_of(obj, TYPE_ARRAY):
|
||||||
|
coordinates = obj
|
||||||
|
elif 'features' in obj:
|
||||||
|
for feat in obj['features']:
|
||||||
|
coordinates = feat['geometry']['coordinates']
|
||||||
|
elif 'geometry' in obj:
|
||||||
|
coordinates = obj['geometry']['coordinates']
|
||||||
|
else:
|
||||||
|
coordinates = obj.get('coordinates', obj)
|
||||||
|
|
||||||
|
var v: = []
|
||||||
|
for e in coordinates:
|
||||||
|
if isinstance(e, [TYPE_FLOAT, TYPE_INT]):
|
||||||
|
v.append(coordinates)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
for f in _coords(e):
|
||||||
|
v.append([f[0], f[1]])
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
func func1(bbox: Array, coords: Array):
|
||||||
|
var w = bbox[0]
|
||||||
|
var s = bbox[1]
|
||||||
|
var e = bbox[2]
|
||||||
|
var n = bbox[3]
|
||||||
|
var lng = coords[0]
|
||||||
|
var lat = coords[1]
|
||||||
|
return [min(w, lng), min(s, lat), max(e, lng), max(n, lat)]
|
||||||
|
|
||||||
|
|
||||||
|
## Возвращает граничную область объекта GeoJSON.
|
||||||
|
## [param obj] - Отображение, геометрия GeoJSON, объект или коллекция объектов.
|
||||||
|
## [returns] [LngLatBbox]
|
||||||
|
func geojson_bounds(obj):
|
||||||
|
var coords: Array = _coords(obj)
|
||||||
|
var wsen = coords.reduce(func1, [180.0, 90.0, -180.0, -90.0])
|
||||||
|
return LngLatBbox.new(wsen[0], wsen[1], wsen[2], wsen[3])
|
||||||
11
scripts/modbus.gd
Normal file
11
scripts/modbus.gd
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready() -> void:
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
pass
|
||||||
@@ -46,8 +46,9 @@ func _ready() -> void:
|
|||||||
var unit = PROTOCOL_TYPES[proto].new(unit_name)
|
var unit = PROTOCOL_TYPES[proto].new(unit_name)
|
||||||
var unit_key = repsettings.get_unit_key(unit_name)
|
var unit_key = repsettings.get_unit_key(unit_name)
|
||||||
units[unit_key] = unit
|
units[unit_key] = unit
|
||||||
unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key))
|
if logger_page:
|
||||||
unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key))
|
unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key))
|
||||||
|
unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key))
|
||||||
|
|
||||||
for key in units: log.info('%s %s:%d' % [units[key].name, key[0], key[1]])
|
for key in units: log.info('%s %s:%d' % [units[key].name, key[0], key[1]])
|
||||||
|
|
||||||
|
|||||||
13
shaders/tilemap.gdshader
Normal file
13
shaders/tilemap.gdshader
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
shader_type canvas_item;
|
||||||
|
|
||||||
|
#include "tools.gdshaderinc"
|
||||||
|
|
||||||
|
uniform vec2 center = vec2(0.5, 0.5);
|
||||||
|
uniform float radius: hint_range(0.0, 1.0) = 0.4; /* Радиус */
|
||||||
|
|
||||||
|
|
||||||
|
void fragment()
|
||||||
|
{
|
||||||
|
COLOR = texture(TEXTURE, UV) * smooth_px(length(UV - center), radius, DS);
|
||||||
|
}
|
||||||
|
|
||||||
6
shaders/tools.gdshaderinc
Normal file
6
shaders/tools.gdshaderinc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const float DS = 0.004; /* Относительное расстояние на котором выполняется сглаживание краёв */
|
||||||
|
|
||||||
|
float smooth_px(float r, float R, float ds)
|
||||||
|
{
|
||||||
|
return 1.0 - smoothstep(R - ds, R + ds, r);
|
||||||
|
}
|
||||||
@@ -45,7 +45,8 @@ func set_node_user_data(i_col: int, i_row: int, val) -> void: set_node_meta(i_co
|
|||||||
func defaultselector_color_proc(_i_row: int): return selector_color
|
func defaultselector_color_proc(_i_row: int): return selector_color
|
||||||
|
|
||||||
|
|
||||||
## Закрепляет заголовок таблицы. [param scrl] - Прокрутка родитель таблицы.
|
## Закрепляет заголовок таблицы.
|
||||||
|
## [param scrl] - Прокрутка родитель таблицы.
|
||||||
## [param prnt] - Родитель прокрутки.
|
## [param prnt] - Родитель прокрутки.
|
||||||
func pin_header(scrl: ScrollContainer, prnt: Control):
|
func pin_header(scrl: ScrollContainer, prnt: Control):
|
||||||
scrl.remove_child(header)
|
scrl.remove_child(header)
|
||||||
|
|||||||
Reference in New Issue
Block a user