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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 92688303a3b5c3353ac85142ccd4a61b17a22ad0c5889ccdac2e85252bcc3b71
4
+ data.tar.gz: f23a95aa6540a93d639e58bb6a0c26c995c691d388782922af1d504c83fe874f
5
+ SHA512:
6
+ metadata.gz: 39f419c0b08bceeedb88b44fe70767a653e878a1f3e42c9b362ac1b98547ccf9eaac0b4ef571f73948ea9281a06340e60a24dd6e5b2d0a3c9828158891716648
7
+ data.tar.gz: d919e1909473b20332e170c31c3c3796f67e8a2d8e238d80c4fe167d94a9e4c935c2812d973dac5cf7c5b09a4b998a499affd2676856af135109fb43a9dd9224
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ src/map/layers/downloads/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://www.rubygems.org"
2
+
3
+ gem "rest-client"
4
+ gem "hokusai-zero", "0.1.9"
data/Gemfile.lock ADDED
@@ -0,0 +1,73 @@
1
+ GEM
2
+ remote: https://www.rubygems.org/
3
+ specs:
4
+ concurrent-ruby (1.3.5)
5
+ domain_name (0.6.20240107)
6
+ ffi (1.17.2)
7
+ ffi (1.17.2-aarch64-linux-gnu)
8
+ ffi (1.17.2-aarch64-linux-musl)
9
+ ffi (1.17.2-arm-linux-gnu)
10
+ ffi (1.17.2-arm-linux-musl)
11
+ ffi (1.17.2-arm64-darwin)
12
+ ffi (1.17.2-x86-linux-gnu)
13
+ ffi (1.17.2-x86-linux-musl)
14
+ ffi (1.17.2-x86_64-darwin)
15
+ ffi (1.17.2-x86_64-linux-gnu)
16
+ ffi (1.17.2-x86_64-linux-musl)
17
+ hokusai-zero (0.1.9)
18
+ concurrent-ruby (~> 1.3.4)
19
+ ffi (~> 1.16)
20
+ memory_profiler
21
+ mini_portile2
22
+ raylib-bindings (~> 0.7.9)
23
+ sdl2-bindings (~> 0.2.3)
24
+ http-accept (1.7.0)
25
+ http-cookie (1.0.8)
26
+ domain_name (~> 0.5)
27
+ logger (1.7.0)
28
+ memory_profiler (1.1.0)
29
+ mime-types (3.7.0)
30
+ logger
31
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
32
+ mime-types-data (3.2025.0520)
33
+ mini_portile2 (2.8.9)
34
+ netrc (0.11.0)
35
+ raylib-bindings (0.7.12)
36
+ ffi (~> 1.16)
37
+ raylib-bindings (0.7.12-aarch64-linux)
38
+ ffi (~> 1.16)
39
+ raylib-bindings (0.7.12-arm64-darwin)
40
+ ffi (~> 1.16)
41
+ raylib-bindings (0.7.12-x86_64-darwin)
42
+ ffi (~> 1.16)
43
+ raylib-bindings (0.7.12-x86_64-linux)
44
+ ffi (~> 1.16)
45
+ rest-client (2.1.0)
46
+ http-accept (>= 1.7.0, < 2.0)
47
+ http-cookie (>= 1.0.2, < 2.0)
48
+ mime-types (>= 1.16, < 4.0)
49
+ netrc (~> 0.8)
50
+ sdl2-bindings (0.2.3)
51
+ ffi (~> 1.15)
52
+
53
+ PLATFORMS
54
+ aarch64-linux
55
+ aarch64-linux-gnu
56
+ aarch64-linux-musl
57
+ arm-linux-gnu
58
+ arm-linux-musl
59
+ arm64-darwin
60
+ ruby
61
+ x86-linux-gnu
62
+ x86-linux-musl
63
+ x86_64-darwin
64
+ x86_64-linux
65
+ x86_64-linux-gnu
66
+ x86_64-linux-musl
67
+
68
+ DEPENDENCIES
69
+ hokusai-zero (= 0.1.9)
70
+ rest-client
71
+
72
+ BUNDLED WITH
73
+ 2.6.8
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Trollio Maps
2
+
3
+ # Map component for Hokusai
4
+
5
+ ![](./mapreversegeocode.gif)
Binary file
data/bin/trollio-maps ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mini_portile2'
3
+ require "pathname"
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
5
+ Pathname.new(__FILE__).realpath)
6
+
7
+ require 'rubygems'
8
+ require 'bundler/setup'
9
+
10
+ if MiniPortile.darwin?
11
+ ENV["RAYLIB_PATH"] = "libraylib.dylib"
12
+ elsif MiniPortile.linux?
13
+ ENV["RAYLIB_PATH"] = "libraylib.so"
14
+ elsif MiniPortile.windows?
15
+ ENV["RAYLIB_PATH"] = "raylib.dll"
16
+ end
17
+
18
+ require_relative "../src/map"
19
+
20
+ Hokusai::Backends::RaylibBackend.run(App) do |config|
21
+ config.title = "Trollio Maps"
22
+ config.width = 800
23
+ config.height = 800
24
+
25
+ config.after_load do
26
+ pp TrollioConfig.asset("Inter-Regular.ttf")
27
+ Hokusai.fonts.register "inter", Hokusai::Backends::RaylibBackend::Font.from(TrollioConfig.asset("Inter-Regular.ttf"))
28
+ Hokusai.fonts.activate "inter"
29
+ end
30
+ end
31
+
32
+
Binary file
data/src/config.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "pathname"
2
+
3
+ class TrollioConfig
4
+ class << self
5
+ def assets_path
6
+ Pathname.new(__dir__).join("..").join("assets")
7
+ end
8
+
9
+ def asset(name)
10
+ assets_path.join(name).to_s
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,79 @@
1
+ module Mapa
2
+ class Control < Hokusai::Block
3
+ style <<~EOF
4
+ [style]
5
+ container {
6
+ background: rgb(206, 206, 206);
7
+ outline: outline(1.0, 1.0, 1.0, 1.0);
8
+ outline_color: rgb(136, 136, 136);
9
+ rounding: 0.2;
10
+ }
11
+
12
+ border {
13
+ outline: outline(0.0, 0.0, 1.0, 0.0);
14
+ outline_color: rgb(136, 136, 136);
15
+ }
16
+
17
+ zoomIn {
18
+ content: "+";
19
+ size: 20;
20
+ padding: padding(10.0, 0.0, 0.0, 12.5);
21
+ }
22
+
23
+ zoomOut {
24
+ content: "-";
25
+ size: 20;
26
+ padding: padding(10.0, 0.0, 0.0, 12.5);
27
+ }
28
+ EOF
29
+
30
+ template <<~EOF
31
+ [template]
32
+ vblock {
33
+ @mousedown="stop"
34
+ @mouseup="stop"
35
+ ...container
36
+ }
37
+ rect.zoomin {
38
+ @click="emit_zoom_in"
39
+ ...border
40
+ }
41
+ label { ...zoomIn }
42
+ rect.zoomout {
43
+ @click="emit_zoom_out"
44
+ }
45
+ label { ...zoomOut }
46
+ EOF
47
+
48
+ uses(
49
+ vblock: Hokusai::Blocks::Vblock,
50
+ rect: Hokusai::Blocks::Rect,
51
+ label: Hokusai::Blocks::Label
52
+ )
53
+
54
+ def stop(event)
55
+ # pp ["stopping", event.captures.map(&:class)]
56
+ event.stop
57
+ end
58
+
59
+ def emit_zoom_in(event)
60
+ pp ["clicked zoomin"]
61
+ event.stop
62
+ emit("zoomin")
63
+ end
64
+
65
+ def emit_zoom_out(event)
66
+ pp ["clicked zoomout"]
67
+ event.stop
68
+ emit("zoomout")
69
+ end
70
+
71
+ def render(canvas)
72
+ canvas.x += 10.0
73
+ canvas.y += 10.0
74
+ canvas.width = 35.0
75
+
76
+ yield canvas
77
+ end
78
+ end
79
+ end
data/src/map/error.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Mapa
2
+ class Error < ::StandardError; end
3
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "./earth"
2
+
3
+ module Mapa
4
+ class EPSG3857 < Earth
5
+ class << self
6
+ def projection
7
+ SphericalMercator
8
+ end
9
+
10
+ def transformation
11
+ @transformation ||= begin
12
+ scale = 0.5 / (Math::PI * SphericalMercator::R)
13
+ Transformation.new(scale, 0.5, -scale, 0.5)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ module Mapa
2
+ class Earth < CRS
3
+ WRAP_LNG = [-180, 180]
4
+ R = 6371000
5
+
6
+ class << self
7
+ def distance(a, b)
8
+ rad = Math::PI / 180
9
+ lat1 = a.lat * rad
10
+ lat2 = b.lat * rad
11
+
12
+ sin_d_lat = Math.sin((b.lat - a.lat) * rad / 2)
13
+ sin_d_lon = Math.sin((b.lng - a.lng) * rad / 2)
14
+
15
+ a = sin_d_lat * sin_d_lat + Math.cos(lat1) * Math.cos(lat2) * sin_d_lon * sin_d_lon
16
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
17
+
18
+ Earth::R * c
19
+ end
20
+
21
+ def wrap_lat_lng(latlng)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,51 @@
1
+ module Mapa
2
+ class CRS
3
+ class << self
4
+ def get(code)
5
+ {
6
+ "EPSG3857" => EPSG3857,
7
+ }
8
+ end
9
+
10
+ def lat_lng_to_point(latlng, zoom)
11
+ point = projection.project(latlng)
12
+ scale = scale(zoom)
13
+
14
+ transformation.transform(point, scale)
15
+ end
16
+
17
+ def point_to_lat_lng(point, zoom)
18
+ scale = scale(zoom)
19
+ untransformed = transformation.untransform(point, scale)
20
+
21
+ projection.unproject(untransformed)
22
+ end
23
+
24
+ def project(latlng)
25
+ end
26
+
27
+ def unproject(point)
28
+ end
29
+
30
+ def scale(zoom)
31
+ 256 * 2 ** zoom;
32
+ end
33
+
34
+ def zoom(scale)
35
+ end
36
+
37
+ def projected_bounds(zoom)
38
+ b = projection.bounds
39
+ s = scale(zoom)
40
+ min = transformation.transform(b.min, s)
41
+ max = transformation.transform(b.max, s)
42
+ Bounds.new(min, max)
43
+ end
44
+
45
+ def wrap_lat_lng(latlng)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ require_relative "./crs/EPSG3857"
@@ -0,0 +1,57 @@
1
+ require "json"
2
+
3
+ module Mapa
4
+ class LatLng
5
+ def self.from(array)
6
+ new(array[0], array[1])
7
+ end
8
+
9
+ attr_accessor :lat, :lng, :alt
10
+
11
+ def initialize(lat, lng, altitude = nil)
12
+ @lat = lat
13
+ @lng = lng
14
+ @alt = altitude
15
+ end
16
+
17
+ def ==(latlng, max_margin = nil)
18
+ margin = [(lat - latlng.lat).abs, (lng - latlng.lng).abs].max
19
+
20
+ margin <= (max_margin || 1.0E-9)
21
+ end
22
+
23
+ def reverse_geo
24
+ pp ["latlng", lat, lng]
25
+ body = JSON.parse(RestClient.get("https://geoffrey.skinnyjames.net/api/v1/us/reverse-geocode?lat=#{lat}&lng=#{lng}").body, symbolize_names: true)
26
+ body[0]
27
+ end
28
+
29
+ def to_point(crs, zoom)
30
+ crs.lat_lng_to_point(self, zoom)
31
+ end
32
+
33
+ def to_s(precision = 4)
34
+ "LatLng(#{lat.round(precision)}, #{lng.round(precision)})"
35
+ end
36
+
37
+ def distance_to(other)
38
+ Earth.distance(self, other)
39
+ end
40
+
41
+ def wrap
42
+ Earth.wrap_lat_lng(self)
43
+ end
44
+
45
+ def to_bounds(meters)
46
+ lat_accuracy = 100 * meters / 40075017
47
+ lng_accuracy = lat_accuracy / Math.cos((Math::PI / 180) * lat)
48
+
49
+ LatLngBounds.new(
50
+ lat - lat_accuracy,
51
+ lng - lng_accuracy,
52
+ lat + lat_accuracy,
53
+ lng + lng_accuracy
54
+ )
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,59 @@
1
+ module Mapa
2
+ class LatLngBounds
3
+ attr_accessor :south_west, :north_east
4
+
5
+ def initialize(bl, tr)
6
+ sw_lat = bl.is_a?(Array) ? bl[0] : bl.lat
7
+ sw_lng = tr.is_a?(Array) ? bl[1] : bl.lng
8
+ ne_lat = bl.is_a?(Array) ? tr[0] : tr.lat
9
+ ne_lng = tr.is_a?(Array) ? tr[1] : tr.lng
10
+
11
+ @south_west = LatLng.new(sw_lat, sw_lng)
12
+ @north_east = LatLng.new(ne_lat, ne_lng)
13
+ end
14
+
15
+ def pad(buffer_ratio)
16
+ height_buffer = Math.abs(south_west.lat - north_east.lat) * buffer_ratio
17
+ width_buffer = Math.abs(south_west.lng - north_east.lng) * buffer_ratio
18
+
19
+ LatLngBounds.new(
20
+ LatLng.new(south_west.lat - height_buffer, south_west.lng - width_buffer),
21
+ LatLng.new(north_east.lat + height_buffer, north_east.lng + width_buffer)
22
+ )
23
+ end
24
+
25
+ def center
26
+ LatLng.new(
27
+ (south_west.lat + north_east.lat) / 2,
28
+ (south_west.lng + north_east.lng) / 2
29
+ )
30
+ end
31
+
32
+ def north_west
33
+ LatLng.new(north_east.lat, south_west.lng)
34
+ end
35
+
36
+ def south_east
37
+ LatLng.new(south_west.lat, north_east.lng)
38
+ end
39
+
40
+ def contains(obj)
41
+ case obj
42
+ when LatLng
43
+ (obj.lat >= south_west.lat) && (obj.lat <= north_east.lat) &&
44
+ (obj.lng >= south_west.lng) && (obj.lng <= north_east.lng)
45
+ when LatLngBounds
46
+ (obj.south_west.lat >= south_west.lat) && (obj.north_east.lat <= north_east.lat) &&
47
+ (obj.south_west.lng >= south_west.lng) && (obj.south_west.lng <= north_east.lng)
48
+ else
49
+ raise Error.new("Must pass LatLng or LatLngBounds")
50
+ end
51
+ end
52
+
53
+ def intersects(bounds)
54
+ end
55
+
56
+ def overlaps(bounds)
57
+ end
58
+ end
59
+ end
File without changes
File without changes
@@ -0,0 +1,32 @@
1
+ module Mapa
2
+ class SphericalMercator
3
+ R = 6378137
4
+ MAX_LATITUDE = 85.0511287798
5
+
6
+ class << self
7
+ def project(latlng)
8
+ d = Math::PI / 180
9
+ lat = [[MAX_LATITUDE, latlng.lat].min, -MAX_LATITUDE].max
10
+ sin = Math.sin(lat * d)
11
+
12
+ Point.new(
13
+ SphericalMercator::R * latlng.lng * d,
14
+ SphericalMercator::R * Math.log((1 + sin) / (1 - sin)) / 2)
15
+ end
16
+
17
+ def unproject(point)
18
+ d = 180 / Math::PI
19
+
20
+ LatLng.new(
21
+ (2 * Math.atan(Math.exp(point.y / SphericalMercator::R)) - (Math::PI / 2)) * d,
22
+ point.x * d / R
23
+ )
24
+ end
25
+
26
+ def bounds
27
+ d = MAX_LATITUDE * Math::PI
28
+ Bounds.new([-d,-d], [d, d])
29
+ end
30
+ end
31
+ end
32
+ end
data/src/map/geo.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative "./geo/projections/spherical_mercator"
2
+ require_relative "./geo/crs"
3
+ require_relative "./geo/lat_lng"
4
+ require_relative "./geo/lat_lng_bounds"
@@ -0,0 +1,101 @@
1
+ module Mapa
2
+ class Bounds
3
+ attr_accessor :min, :max
4
+
5
+ def initialize(tl, br)
6
+ min_x = tl.is_a?(Array) ? tl[0] : tl.x
7
+ max_x = br.is_a?(Array) ? br[0] : br.x
8
+ min_y = tl.is_a?(Array) ? tl[1] : tl.y
9
+ max_y = br.is_a?(Array) ? br[1] : br.y
10
+
11
+ @min = Point.new(min_x, min_y)
12
+ @max = Point.new(max_x, max_y)
13
+ end
14
+
15
+ def extend(point_or_bounds)
16
+ case point_or_bounds
17
+ when Point
18
+ self.min.x = [point_or_bounds.x, min.x].min
19
+ self.min.y = [point_or_bounds.y, min.y].min
20
+ self.max.x = [point_or_bounds.x, max.x].max
21
+ self.max.y = [point_or_bounds.y, max.y].max
22
+ when Bounds
23
+ self.min.x = [point_or_bounds.min.x, min.x].min
24
+ self.min.y = [point_or_bounds.min.y, min.y].min
25
+ self.max.x = [point_or_bounds.max.x, max.x].max
26
+ self.max.y = [point_or_bounds.max.y, max.y].max
27
+ else
28
+ raise Error.new("Extends requires a Point or Bounds")
29
+ end
30
+ end
31
+
32
+ def to_lat_lng_bounds(crs, zoom)
33
+ pp [crs.point_to_lat_lng(min, zoom), min]
34
+
35
+ tl = crs.point_to_lat_lng(min, zoom)
36
+ br = crs.point_to_lat_lng(max, zoom)
37
+
38
+ sw = LatLng.new(tl.lat, br.lng)
39
+ ne = LatLng.new(br.lat, tl.lng)
40
+ LatLngBounds.new(sw, ne)
41
+ end
42
+
43
+ def center(round = false)
44
+ Point.new(
45
+ (min.x + max.x) / 2,
46
+ (min.y + max.y )/ 2,
47
+ round
48
+ )
49
+ end
50
+
51
+ def bottom_left
52
+ Point.new(min.x, max.y)
53
+ end
54
+
55
+ def top_right
56
+ Point.new(max.x, min.y)
57
+ end
58
+
59
+ def top_left
60
+ min
61
+ end
62
+
63
+ def bottom_right
64
+ max
65
+ end
66
+
67
+ def size
68
+ max - min
69
+ end
70
+
71
+ def contains(point_or_bounds)
72
+ case point_or_bounds
73
+ when Point
74
+ (point_or_bounds.x >= min.x) &&
75
+ (point_or_bounds.x <= max.x) &&
76
+ (point_or_bounds.y >= min.y) &&
77
+ (point_or_bounds.y <= max.y)
78
+ when Bounds
79
+ (point_or_bounds.min.x >= min.x) &&
80
+ (point_or_bounds.max.x <= max.x) &&
81
+ (point_or_bounds.min.y >= min.y) &&
82
+ (point_or_bounds.max.y <= max.y)
83
+ else
84
+ raise Error.new("contains requires a Point or Bounds")
85
+ end
86
+ end
87
+
88
+ def intersects(bounds)
89
+ end
90
+
91
+ def overlaps(bounds)
92
+ end
93
+
94
+ def pad(buffer)
95
+ end
96
+
97
+ def ==(bounds)
98
+ min == bounds.top_left && max == bounds.bottom_right
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,61 @@
1
+ module Mapa
2
+ class BoundsProxy
3
+ attr_reader :map, :screen
4
+
5
+ def initialize(screen, map)
6
+ @map = map
7
+ @screen = screen
8
+ end
9
+
10
+ def top_left
11
+ screen.top_left
12
+ end
13
+
14
+ def bottom_right
15
+ screen.bottom_right
16
+ end
17
+ end
18
+
19
+ class BoundsProxyCollection
20
+ include Enumerable
21
+
22
+ attr_reader :bounds
23
+
24
+ attr_accessor :offset
25
+
26
+ def initialize
27
+ @bounds = []
28
+ @offset = Point.new(0, 0)
29
+ end
30
+
31
+ def each
32
+ bounds.each do |b|
33
+ yield b
34
+ end
35
+ end
36
+
37
+ def <<(proxy)
38
+ @bounds << proxy
39
+ end
40
+
41
+ def top_left
42
+ bounds.map(&:top_left).min
43
+ end
44
+
45
+ def bottom_right
46
+ bounds.map(&:bottom_right).max
47
+ end
48
+
49
+ def map_contains(point)
50
+ bounds.find do |b|
51
+ b.map.contains(point)
52
+ end
53
+ end
54
+
55
+ def contains(point)
56
+ bounds.find do |b|
57
+ b.screen.contains(point)
58
+ end
59
+ end
60
+ end
61
+ end