trollio_maps 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,105 @@
1
+ module Mapa
2
+ class Point
3
+ include Comparable
4
+
5
+ def self.convert(str)
6
+ case str
7
+ when String
8
+ coords = str.gsub(/\[|\]/, '').split(",").map(&:to_f)
9
+ new(coords[0], coords[1])
10
+ when Array
11
+ new(str[0], str[1])
12
+ end
13
+ end
14
+
15
+ def valid?(x, y)
16
+ true
17
+ end
18
+
19
+ attr_accessor :x, :y
20
+
21
+ def initialize(x, y, round = false)
22
+ raise Error.new("Invalid point") unless valid?(x, y)
23
+
24
+ @x = round ? x.round : x
25
+ @y = round ? y.round : y
26
+ end
27
+
28
+ def add!(point)
29
+ self.x += point.x
30
+ self.y += point.y
31
+
32
+ self
33
+ end
34
+
35
+ def <=>(point)
36
+ if x < point.x && y < point.y
37
+ -1
38
+ elsif x > point.x && y > point.y
39
+ 1
40
+ else
41
+ 0
42
+ end
43
+ end
44
+
45
+ def +(point)
46
+ Point.new(x + point.x, y + point.y)
47
+ end
48
+
49
+ def -(point)
50
+ Point.new(x - point.x, y - point.y)
51
+ end
52
+
53
+ def subtract(num)
54
+ Point.new(x - num, y - num)
55
+ end
56
+
57
+ def /(num)
58
+ Point.new(x / num.x, y / num.y)
59
+ end
60
+
61
+ def *(num)
62
+ Point.new(x * num.x, y * num.y)
63
+ end
64
+
65
+ def scale(point)
66
+ Point.new(x * point.x, y * point.y)
67
+ end
68
+
69
+ def unscale(point)
70
+ Point.new(x / point.x, y / point.y)
71
+ end
72
+
73
+ def round
74
+ Point.new(x.round, y.round)
75
+ end
76
+
77
+ def floor
78
+ Point.new(x.floor, y.floor)
79
+ end
80
+
81
+ def ceil
82
+ Point.new(x.ceil, y.ceil)
83
+ end
84
+
85
+ def distance_to(point)
86
+ xx = point.x - x
87
+ yy = point.y - y
88
+
89
+ Math.sqrt(xx ** xx + yy ** yy)
90
+ end
91
+
92
+ def ==(point)
93
+ point.x == x && point.y == y
94
+ end
95
+
96
+ def contains(point)
97
+ Math.abs(point.x) <= Math.abs(x) &&
98
+ Math.abs(point.y) <= Math.abs(y)
99
+ end
100
+
101
+ def to_s
102
+ "Point(#{x}, #{y})"
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,110 @@
1
+ module Mapa
2
+ class TileBounds
3
+ TILE_SIZE = 256
4
+
5
+ attr_reader :center, :screen_bounds, :crs
6
+ attr_accessor :drag_offset, :screen_bounds, :center
7
+
8
+ def initialize(crs, latlng, screen_bounds)
9
+ @crs = crs
10
+ @center = latlng
11
+ @screen_bounds = screen_bounds
12
+ @tiles = {}
13
+ @drag_offset = Point.new(0, 0)
14
+ end
15
+
16
+ def geo_bounds(bounds, zoom)
17
+ bounds.to_lat_lng_bounds(crs, zoom)
18
+ end
19
+
20
+ def offset(zoom)
21
+ collection(zoom).offset
22
+ end
23
+
24
+ def coords_at(point, zoom)
25
+ collection = collection(zoom)
26
+ tile = collection.contains(point)
27
+ offset = collection.offset
28
+
29
+ point.x = point.x - tile.screen.center.x + offset.x
30
+ point.y = point.y - tile.screen.center.y + offset.y
31
+
32
+ # click point x / screen width = ratio
33
+ nx = (point.x) / (tile.screen.max.x - tile.screen.min.x)
34
+ # click poidnt y / screen_height = ratio
35
+ ny = (point.y) / (tile.screen.max.y - tile.screen.min.y)
36
+
37
+ mbx = (tile.map.max.x - tile.map.min.x) * nx
38
+ mby = (tile.map.max.y - tile.map.min.y) * ny
39
+
40
+ ax = (tile.map.min.x + mbx)
41
+ ay = (tile.map.min.y + mby)
42
+
43
+ x = Point.new(ax, ay)
44
+ crs.point_to_lat_lng(x, zoom)
45
+ end
46
+
47
+
48
+ def collection(zoom)
49
+ collection = BoundsProxyCollection.new
50
+ tile_center = crs.lat_lng_to_point(center, zoom)
51
+ screen_center = screen_bounds.center + drag_offset
52
+
53
+ offx = tile_center.x.modulo(1)
54
+ offy = tile_center.y.modulo(1)
55
+
56
+ collection.offset = Point.new(offx * TILE_SIZE, offy * TILE_SIZE)
57
+
58
+ sx = screen_center.x
59
+ sy = screen_center.y
60
+
61
+ tile_center = tile_center.floor
62
+ mx = tile_center.x
63
+ my = tile_center.y
64
+
65
+ tp = Point.new(TILE_SIZE, TILE_SIZE)
66
+ mp = Point.new(1, 1)
67
+
68
+ # find top left...
69
+ tl = Point.new(screen_center.x - (TILE_SIZE / 2), screen_center.y - (TILE_SIZE / 2))
70
+ mtl = Point.new(tile_center.x, tile_center.y)
71
+
72
+ while tl.x >= screen_bounds.min.x || tl.y >= screen_bounds.min.y
73
+ tl = tl - tp
74
+ mtl = mtl - mp
75
+ end
76
+
77
+ sx = tl.x
78
+ sy = tl.y
79
+ mx = mtl.x
80
+ my = mtl.y
81
+
82
+ # populate tiles
83
+ while sx <= screen_bounds.max.x + TILE_SIZE
84
+ sy = tl.y
85
+ my = mtl.y
86
+
87
+ while sy <= screen_bounds.max.y + TILE_SIZE
88
+ sb = Bounds.new(
89
+ Point.new(sx, sy),
90
+ Point.new(sx + tp.x, sy + tp.y)
91
+ )
92
+
93
+ mb = Bounds.new(
94
+ Point.new(mx, my),
95
+ Point.new(mx + mp.x, my + mp.y)
96
+ )
97
+
98
+ collection << BoundsProxy.new(sb, mb)
99
+ my += 1
100
+ sy += TILE_SIZE
101
+ end
102
+
103
+ mx += 1
104
+ sx += TILE_SIZE
105
+ end
106
+
107
+ collection
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,26 @@
1
+ module Mapa
2
+ class Transformation
3
+ attr_reader :a, :b, :c, :d
4
+
5
+ def initialize(a, b, c, d)
6
+ @a = a
7
+ @b = b
8
+ @c = c
9
+ @d = d
10
+ end
11
+
12
+ def transform(point, scale = 1)
13
+ Point.new(
14
+ scale * (a * point.x + b),
15
+ scale * (c * point.y + d)
16
+ )
17
+ end
18
+
19
+ def untransform(point, scale = 1)
20
+ Point.new(
21
+ (point.x / scale - b) / a,
22
+ (point.y / scale - d) / c
23
+ )
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ require_relative "./geometry/bounds"
2
+ require_relative "./geometry/point"
3
+ require_relative "./geometry/transform"
4
+ require_relative "./geometry/bounds_collection"
5
+ require_relative "./geometry/tile_bounds"
@@ -0,0 +1,30 @@
1
+ module Mapa
2
+ class MarkerLayer < Hokusai::Block
3
+ template <<~EOF
4
+ [template]
5
+ virtual
6
+ EOF
7
+
8
+ computed :marker, default: nil
9
+
10
+ inject :zoom
11
+ inject :center
12
+ inject :container_size
13
+ inject :tile_bounds
14
+
15
+ def on_mounted
16
+ node.meta.set_prop(:z, "3")
17
+ node.meta.set_prop(:ztarget, "parent")
18
+ end
19
+
20
+ def render(canvas)
21
+ return if marker.nil?
22
+
23
+ draw do
24
+ circle(marker.x, marker.y, 5) do |command|
25
+ command.color = Hokusai::Color.new(44, 68, 224)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,176 @@
1
+ require "fileutils"
2
+ require "rest-client"
3
+ module Mapa
4
+ class TileLayer < Hokusai::Block
5
+ template <<~EOF
6
+ [template]
7
+ empty {
8
+ @mousedown="start_drag"
9
+ @mousemove="drag"
10
+ @mouseup="end_drag"
11
+ }
12
+ EOF
13
+
14
+ uses(
15
+ empty: Hokusai::Blocks::Empty
16
+ )
17
+
18
+ computed! :url
19
+ computed :tile_size, default: 256, convert: proc(&:to_i)
20
+ computed :min_zoom, default: 0, convert: proc(&:to_i)
21
+ computed :max_zoom, default: 18, convert: proc(&:to_i)
22
+ computed :subdomains, default: ['a', 'b', 'c']
23
+ computed :error_tile_url, default: ''
24
+ computed :zoom_offset, default: 9, convert: proc(&:to_i)
25
+ computed :tms, default: false
26
+ computed :zoom_reverse, default: false
27
+ computed :detect_retina, default: false
28
+ computed :cross_origin, default: false
29
+ computed :referrer_policy, default: false
30
+
31
+ inject :zoom
32
+ inject :center
33
+ inject :container_size
34
+ inject :tile_bounds
35
+
36
+ attr_reader :tiles, :levels, :size, :update_interval, :dragging
37
+
38
+ def initialize(**args)
39
+ super
40
+
41
+ @tiles = {}
42
+ @dragging = false
43
+ @drag_start = nil
44
+ end
45
+
46
+ def start_drag(event)
47
+ pp ["Started drag"]
48
+ if !@dragging && event.left.down
49
+ @dragging = true
50
+ @drag_start = Point.new(event.pos.x, event.pos.y)
51
+ end
52
+ end
53
+
54
+ def drag(event)
55
+ if event.left.down && dragging
56
+ tile_bounds.drag_offset = Point.new(event.pos.x, event.pos.y) - @drag_start
57
+ # @last = tile_bounds.coords_at(tile_bounds.screen_bounds.center, zoom)
58
+ end
59
+ end
60
+
61
+ def end_drag(event)
62
+ if dragging && event.left.up
63
+ @dragging = false
64
+ if tile_bounds.drag_offset.x > 5 || tile_bounds.drag_offset.y > 5
65
+
66
+ collection = tile_bounds.collection(zoom)
67
+ off = tile_bounds.drag_offset
68
+
69
+ pp [collection.offset]
70
+
71
+ c = tile_bounds.screen_bounds.center
72
+
73
+
74
+
75
+ center = tile_bounds.coords_at(c, zoom)
76
+
77
+ pp ["unset drag offset", center]
78
+
79
+
80
+ tile_bounds.drag_offset = Point.new(0, 0)
81
+ tile_bounds.center = center
82
+
83
+ emit("update", center)
84
+ end
85
+
86
+ @drag_start = nil
87
+ end
88
+ end
89
+
90
+ def downloads
91
+ FileUtils.mkdir_p "#{__dir__}/downloads"
92
+
93
+ "#{__dir__}/downloads"
94
+ end
95
+
96
+ def fetch_download(url)
97
+ path = "#{downloads}/#{url.gsub("/", "-")}"
98
+
99
+ if File.exist?(path)
100
+ @tiles[url] = path
101
+ path
102
+ else
103
+ Thread.new do
104
+ block = proc do |res|
105
+ File.open(path, "wb") do |io|
106
+ res.read_body do |chunk|
107
+ io.write chunk
108
+ end
109
+ end
110
+
111
+ @tiles[url] = path
112
+
113
+ end
114
+
115
+ RestClient::Request.execute(method: :get, url: url, block_response: block)
116
+ end
117
+ nil
118
+ end
119
+ end
120
+
121
+ def subdomain(coords)
122
+ index = (coords.x + coords.y % subdomains.size).abs
123
+ subdomains[index]
124
+ end
125
+
126
+ def zoom_for_url
127
+ if zoom_reverse
128
+ max_zoom - zoom
129
+ else
130
+ zoom + zoom_offset
131
+ end
132
+ end
133
+
134
+ # https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
135
+ def tile_url(coords)
136
+ opts = {
137
+ s: subdomain(coords),
138
+ z: zoom_for_url,
139
+ x: coords.x.to_i,
140
+ y: coords.y.to_i,
141
+ r: ''
142
+ }
143
+
144
+ url.gsub(/\{ *([\w_ -]+) *\}/) do |match|
145
+ opts[match[1].to_sym]
146
+ end
147
+ end
148
+
149
+ def before_updated
150
+ if @throttled
151
+ if (Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @ttime) > 200
152
+ @throttled = false
153
+ end
154
+ end
155
+ end
156
+
157
+ def render(canvas)
158
+ # @canvas = canvas
159
+ collection = tile_bounds.collection(zoom)
160
+ offx = collection.offset.x
161
+ offy = collection.offset.y
162
+
163
+
164
+ draw do
165
+ collection.each_with_index do |item, i|
166
+ # next if i.odd?
167
+ if file = fetch_download(tile_url(item.map.center))
168
+ image(file, item.screen.center.x - offx, item.screen.center.y - offy, tile_size, tile_size)
169
+ end
170
+ end
171
+ end
172
+
173
+ yield canvas
174
+ end
175
+ end
176
+ end
data/src/map/layers.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative "./layers/tile_layer"
2
+ require_relative "./layers/marker_layer"
data/src/map.rb ADDED
@@ -0,0 +1,239 @@
1
+ require "hokusai"
2
+ require "hokusai/backends/raylib"
3
+ require_relative "./map/error"
4
+ require_relative "./map/geo"
5
+ require_relative "./map/layers"
6
+ require_relative "./map/geometry"
7
+ require_relative "./map/control"
8
+ require_relative "./config"
9
+
10
+ module Mapa
11
+ class Map < Hokusai::Block
12
+ template <<~EOF
13
+ [template]
14
+ vblock
15
+ slot
16
+ EOF
17
+
18
+ uses(vblock: Hokusai::Blocks::Vblock)
19
+
20
+ attr_reader :size
21
+ attr_accessor :sdrag
22
+
23
+ provide :zoom, :zoom
24
+ provide :center, :center
25
+ provide :container_size, :size
26
+ provide :tile_bounds, :tile_bounds
27
+
28
+ computed! :center
29
+ computed :crs, default: "EPSG3857"
30
+ computed :zoom, default: 1
31
+ computed :min_zoom, default: nil
32
+ computed :max_zoom, default: nil
33
+ computed :update_center, default: nil
34
+
35
+ def before_updated
36
+ if update_center
37
+ @bounds = TileBounds.new(Mapa::EPSG3857, update_center, size)
38
+ emit("clear_update_center")
39
+ end
40
+ end
41
+
42
+ def start_drag(event)
43
+ if event.left.down && !@started
44
+ self.sdrag = [event.pos.x.clone, event.pos.y.clone]
45
+ @started = true
46
+ end
47
+ end
48
+
49
+ def on_resize(canvas)
50
+ @size = Bounds.new([canvas.x, canvas.y], [canvas.x + canvas.width, canvas.y + canvas.height])
51
+ @bounds = TileBounds.new(Mapa::EPSG3857, center, size)
52
+ end
53
+
54
+ def center_point
55
+ Mapa::EPSG3857.lat_lng_to_point(center, zoom)
56
+ end
57
+
58
+ def alert_latlng(event)
59
+ if sdrag && ((event.pos.x - sdrag[0]).abs > 5 || (event.pos.y - sdrag[1]).abs > 5)
60
+ @started = false
61
+ self.sdrag = nil
62
+ return
63
+ end
64
+
65
+ if event.left.released
66
+ point = Point.new(event.pos.x, event.pos.y)
67
+ clicked = Point.new(event.pos.x, event.pos.y)
68
+ emit("change", tile_bounds.coords_at(point, zoom), clicked)
69
+ @started = false
70
+ end
71
+ end
72
+
73
+ def tile_bounds
74
+ @bounds ||= TileBounds.new(Mapa::EPSG3857, center, size)
75
+ end
76
+
77
+ def render(canvas)
78
+ if @size.nil?
79
+ on_resize(canvas)
80
+ end
81
+
82
+ yield canvas
83
+ end
84
+ end
85
+ end
86
+
87
+ class App < Hokusai::Block
88
+ style <<~EOF
89
+ [style]
90
+ tileStyle {
91
+ url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
92
+ zoom_offset: 8;
93
+ }
94
+ box {
95
+ background: rgb(48, 48, 48);
96
+ height: 100.0;
97
+ padding: padding(20.0, 0.0, 30.0, 0.0);
98
+ }
99
+
100
+ input {
101
+ background: rgb(255,255,255);
102
+ color: rgb(22,22,22);
103
+ padding: padding(5.0, 15.0, 5.0, 15.0);
104
+ outline: outline(1.0, 1.0, 1.0, 1.0)
105
+ outline_color: rgb(33,33,33);
106
+ height: 50.0;
107
+ rounding: 0.2;
108
+ }
109
+
110
+ label {
111
+ color: rgb(255,255,255);
112
+ }
113
+
114
+ zstyle {
115
+ z: 2;
116
+ ztarget: "parent";
117
+ reverse: true;
118
+ }
119
+ EOF
120
+
121
+ template <<~EOF
122
+ [template]
123
+ vblock
124
+ map {
125
+ @change="update_latlng"
126
+ @clear_update_center="clear_update_center"
127
+ :update_center="update_center"
128
+ cursor="pointer"
129
+ :center="center"
130
+ :zoom="zoom"
131
+ }
132
+ tile_layer {
133
+ ...tileStyle
134
+ }
135
+ marker_layer {:marker="marker"}
136
+ hblock { ...zstyle }
137
+ control {
138
+ @zoomin="zoom_in"
139
+ @zoomout="zoom_out"
140
+ :height="80.0" :width="65.0"
141
+ }
142
+ empty
143
+ vblock { padding="10.0,0.0,0.0,10.0"}
144
+ input { @mousedown="stop" @change="search" ...input }
145
+ EOF
146
+
147
+ uses(
148
+ hblock: Hokusai::Blocks::Hblock,
149
+ vblock: Hokusai::Blocks::Vblock,
150
+ label: Hokusai::Blocks::Label,
151
+ input: Hokusai::Blocks::Input,
152
+ empty: Hokusai::Blocks::Empty,
153
+ control: Mapa::Control,
154
+ button: Hokusai::Blocks::Button,
155
+ map: Mapa::Map,
156
+ tile_layer: Mapa::TileLayer,
157
+ marker_layer: Mapa::MarkerLayer
158
+ )
159
+
160
+ attr_reader :drag_offset, :update_center
161
+
162
+ def stop(event)
163
+ event.stop
164
+ end
165
+
166
+ def point
167
+ @point || "no point"
168
+ end
169
+
170
+ def initialize(**args)
171
+ super
172
+
173
+ @dragging = false
174
+ end
175
+
176
+ def marker
177
+ @marker
178
+ end
179
+
180
+ def search(term)
181
+ begin
182
+ res = JSON.parse RestClient.get("https://geoffrey.skinnyjames.net/api/v1/us/geocode/#{CGI.escapeURIComponent(term)}").body, symbolize_names: true
183
+ res = res.first
184
+ pp res
185
+ @update_center = Mapa::LatLng.new(res[:latitude], res[:longitude])
186
+ @marker = Mapa::Point.new(400, 400)
187
+ rescue
188
+ pp ["point can't be located"]
189
+ end
190
+ end
191
+
192
+ def update_latlng(latlng, point)
193
+ pp ["changing"]
194
+ @marker = point.clone
195
+ add = latlng.reverse_geo
196
+ @point = <<~EOF
197
+ #{latlng}
198
+ #{add[:address_number]} #{add[:street_name]} #{add[:street_posttype]}
199
+ #{add[:postal_community]}, #{add[:state]} #{add[:zip_code]}
200
+ EOF
201
+ end
202
+
203
+ def clear_update_center
204
+ @update_center = nil
205
+ end
206
+
207
+ def zoom_out
208
+ @marker = nil
209
+ @zoom -= 1
210
+ end
211
+
212
+ def zoom
213
+ @zoom ||= 1
214
+ end
215
+
216
+ def center
217
+ @center ||= Mapa::LatLng.new(39.9612, -82.9988)
218
+ end
219
+
220
+ def zoom_in
221
+ @marker = nil
222
+
223
+ @zoom += 1
224
+ end
225
+ end
226
+ #
227
+ # 1 = 9
228
+ # 2 = 10
229
+ # 3 = 11
230
+ #
231
+ #
232
+
233
+ # latlng = Mapa::LatLng.new(39.9612, -82.9988)
234
+ # point = Mapa::EPSG3857.lat_lng_to_point(latlng, 4)
235
+ # new_lat_lng = Mapa::EPSG3857.point_to_lat_lng(point, 4)
236
+
237
+ # map = Mapa::EPSG3857
238
+
239
+ # pp map.projection.project(map.projection.unproject(Mapa::Point.new(0, 0)))