libgd-gis 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.
Files changed (225) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +127 -0
  3. data/examples/argentina.png +0 -0
  4. data/examples/argentina.rb +82 -0
  5. data/examples/argentina_con_localidades.rb +89 -0
  6. data/examples/argentina_museos.rb +70 -0
  7. data/examples/flag.png +0 -0
  8. data/examples/hydro.png +0 -0
  9. data/examples/hydro_plants.geojson +25 -0
  10. data/examples/localidades.json +1 -0
  11. data/examples/museos.png +0 -0
  12. data/examples/museos_datosabiertos.csv +1183 -0
  13. data/examples/output/argentina.png +0 -0
  14. data/examples/output/argentina_localidades.png +0 -0
  15. data/examples/output/argentina_museos.png +0 -0
  16. data/examples/output/tanzania_hydro.png +0 -0
  17. data/examples/render_tile.rb +31 -0
  18. data/examples/tanzania.rb +32 -0
  19. data/examples/target.rb +19 -0
  20. data/examples/tmp/tiles/7_27_43.png +0 -0
  21. data/examples/tmp/tiles/7_27_44.png +0 -0
  22. data/examples/tmp/tiles/7_27_45.png +0 -0
  23. data/examples/tmp/tiles/7_27_46.png +0 -0
  24. data/examples/tmp/tiles/7_27_47.png +0 -0
  25. data/examples/tmp/tiles/7_27_48.png +0 -0
  26. data/examples/tmp/tiles/7_27_49.png +0 -0
  27. data/examples/tmp/tiles/7_27_50.png +0 -0
  28. data/examples/tmp/tiles/7_28_43.png +0 -0
  29. data/examples/tmp/tiles/7_28_44.png +0 -0
  30. data/examples/tmp/tiles/7_28_45.png +0 -0
  31. data/examples/tmp/tiles/7_28_46.png +0 -0
  32. data/examples/tmp/tiles/7_28_47.png +0 -0
  33. data/examples/tmp/tiles/7_28_48.png +0 -0
  34. data/examples/tmp/tiles/7_28_49.png +0 -0
  35. data/examples/tmp/tiles/7_28_50.png +0 -0
  36. data/examples/tmp/tiles/7_29_43.png +0 -0
  37. data/examples/tmp/tiles/7_29_44.png +0 -0
  38. data/examples/tmp/tiles/7_29_45.png +0 -0
  39. data/examples/tmp/tiles/7_29_46.png +0 -0
  40. data/examples/tmp/tiles/7_29_47.png +0 -0
  41. data/examples/tmp/tiles/7_29_48.png +0 -0
  42. data/examples/tmp/tiles/7_29_49.png +0 -0
  43. data/examples/tmp/tiles/7_29_50.png +0 -0
  44. data/examples/tmp/tiles/7_30_43.png +0 -0
  45. data/examples/tmp/tiles/7_30_44.png +0 -0
  46. data/examples/tmp/tiles/7_30_45.png +0 -0
  47. data/examples/tmp/tiles/7_30_46.png +0 -0
  48. data/examples/tmp/tiles/7_30_47.png +0 -0
  49. data/examples/tmp/tiles/7_30_48.png +0 -0
  50. data/examples/tmp/tiles/7_30_49.png +0 -0
  51. data/examples/tmp/tiles/7_30_50.png +0 -0
  52. data/examples/tmp/tiles/7_31_43.png +0 -0
  53. data/examples/tmp/tiles/7_31_44.png +0 -0
  54. data/examples/tmp/tiles/7_31_45.png +0 -0
  55. data/examples/tmp/tiles/7_31_46.png +0 -0
  56. data/examples/tmp/tiles/7_31_47.png +0 -0
  57. data/examples/tmp/tiles/7_31_48.png +0 -0
  58. data/examples/tmp/tiles/7_31_49.png +0 -0
  59. data/examples/tmp/tiles/7_31_50.png +0 -0
  60. data/examples/tmp/tiles/7_32_43.png +0 -0
  61. data/examples/tmp/tiles/7_32_44.png +0 -0
  62. data/examples/tmp/tiles/7_32_45.png +0 -0
  63. data/examples/tmp/tiles/7_32_46.png +0 -0
  64. data/examples/tmp/tiles/7_32_47.png +0 -0
  65. data/examples/tmp/tiles/7_32_48.png +0 -0
  66. data/examples/tmp/tiles/7_32_49.png +0 -0
  67. data/examples/tmp/tiles/7_32_50.png +0 -0
  68. data/examples/tmp/tiles/7_37_71.png +0 -0
  69. data/examples/tmp/tiles/7_37_72.png +0 -0
  70. data/examples/tmp/tiles/7_37_73.png +0 -0
  71. data/examples/tmp/tiles/7_37_74.png +0 -0
  72. data/examples/tmp/tiles/7_37_75.png +0 -0
  73. data/examples/tmp/tiles/7_37_76.png +0 -0
  74. data/examples/tmp/tiles/7_37_77.png +0 -0
  75. data/examples/tmp/tiles/7_37_78.png +0 -0
  76. data/examples/tmp/tiles/7_37_79.png +0 -0
  77. data/examples/tmp/tiles/7_37_80.png +0 -0
  78. data/examples/tmp/tiles/7_37_81.png +0 -0
  79. data/examples/tmp/tiles/7_37_82.png +0 -0
  80. data/examples/tmp/tiles/7_37_83.png +0 -0
  81. data/examples/tmp/tiles/7_37_84.png +0 -0
  82. data/examples/tmp/tiles/7_37_85.png +0 -0
  83. data/examples/tmp/tiles/7_37_86.png +0 -0
  84. data/examples/tmp/tiles/7_37_87.png +0 -0
  85. data/examples/tmp/tiles/7_38_71.png +0 -0
  86. data/examples/tmp/tiles/7_38_72.png +0 -0
  87. data/examples/tmp/tiles/7_38_73.png +0 -0
  88. data/examples/tmp/tiles/7_38_74.png +0 -0
  89. data/examples/tmp/tiles/7_38_75.png +0 -0
  90. data/examples/tmp/tiles/7_38_76.png +0 -0
  91. data/examples/tmp/tiles/7_38_77.png +0 -0
  92. data/examples/tmp/tiles/7_38_78.png +0 -0
  93. data/examples/tmp/tiles/7_38_79.png +0 -0
  94. data/examples/tmp/tiles/7_38_80.png +0 -0
  95. data/examples/tmp/tiles/7_38_81.png +0 -0
  96. data/examples/tmp/tiles/7_38_82.png +0 -0
  97. data/examples/tmp/tiles/7_38_83.png +0 -0
  98. data/examples/tmp/tiles/7_38_84.png +0 -0
  99. data/examples/tmp/tiles/7_38_85.png +0 -0
  100. data/examples/tmp/tiles/7_38_86.png +0 -0
  101. data/examples/tmp/tiles/7_38_87.png +0 -0
  102. data/examples/tmp/tiles/7_39_71.png +0 -0
  103. data/examples/tmp/tiles/7_39_72.png +0 -0
  104. data/examples/tmp/tiles/7_39_73.png +0 -0
  105. data/examples/tmp/tiles/7_39_74.png +0 -0
  106. data/examples/tmp/tiles/7_39_75.png +0 -0
  107. data/examples/tmp/tiles/7_39_76.png +0 -0
  108. data/examples/tmp/tiles/7_39_77.png +0 -0
  109. data/examples/tmp/tiles/7_39_78.png +0 -0
  110. data/examples/tmp/tiles/7_39_79.png +0 -0
  111. data/examples/tmp/tiles/7_39_80.png +0 -0
  112. data/examples/tmp/tiles/7_39_81.png +0 -0
  113. data/examples/tmp/tiles/7_39_82.png +0 -0
  114. data/examples/tmp/tiles/7_39_83.png +0 -0
  115. data/examples/tmp/tiles/7_39_84.png +0 -0
  116. data/examples/tmp/tiles/7_39_85.png +0 -0
  117. data/examples/tmp/tiles/7_39_86.png +0 -0
  118. data/examples/tmp/tiles/7_39_87.png +0 -0
  119. data/examples/tmp/tiles/7_40_71.png +0 -0
  120. data/examples/tmp/tiles/7_40_72.png +0 -0
  121. data/examples/tmp/tiles/7_40_73.png +0 -0
  122. data/examples/tmp/tiles/7_40_74.png +0 -0
  123. data/examples/tmp/tiles/7_40_75.png +0 -0
  124. data/examples/tmp/tiles/7_40_76.png +0 -0
  125. data/examples/tmp/tiles/7_40_77.png +0 -0
  126. data/examples/tmp/tiles/7_40_78.png +0 -0
  127. data/examples/tmp/tiles/7_40_79.png +0 -0
  128. data/examples/tmp/tiles/7_40_80.png +0 -0
  129. data/examples/tmp/tiles/7_40_81.png +0 -0
  130. data/examples/tmp/tiles/7_40_82.png +0 -0
  131. data/examples/tmp/tiles/7_40_83.png +0 -0
  132. data/examples/tmp/tiles/7_40_84.png +0 -0
  133. data/examples/tmp/tiles/7_40_85.png +0 -0
  134. data/examples/tmp/tiles/7_40_86.png +0 -0
  135. data/examples/tmp/tiles/7_40_87.png +0 -0
  136. data/examples/tmp/tiles/7_41_71.png +0 -0
  137. data/examples/tmp/tiles/7_41_72.png +0 -0
  138. data/examples/tmp/tiles/7_41_73.png +0 -0
  139. data/examples/tmp/tiles/7_41_74.png +0 -0
  140. data/examples/tmp/tiles/7_41_75.png +0 -0
  141. data/examples/tmp/tiles/7_41_76.png +0 -0
  142. data/examples/tmp/tiles/7_41_77.png +0 -0
  143. data/examples/tmp/tiles/7_41_78.png +0 -0
  144. data/examples/tmp/tiles/7_41_79.png +0 -0
  145. data/examples/tmp/tiles/7_41_80.png +0 -0
  146. data/examples/tmp/tiles/7_41_81.png +0 -0
  147. data/examples/tmp/tiles/7_41_82.png +0 -0
  148. data/examples/tmp/tiles/7_41_83.png +0 -0
  149. data/examples/tmp/tiles/7_41_84.png +0 -0
  150. data/examples/tmp/tiles/7_41_85.png +0 -0
  151. data/examples/tmp/tiles/7_41_86.png +0 -0
  152. data/examples/tmp/tiles/7_41_87.png +0 -0
  153. data/examples/tmp/tiles/7_42_71.png +0 -0
  154. data/examples/tmp/tiles/7_42_72.png +0 -0
  155. data/examples/tmp/tiles/7_42_73.png +0 -0
  156. data/examples/tmp/tiles/7_42_74.png +0 -0
  157. data/examples/tmp/tiles/7_42_75.png +0 -0
  158. data/examples/tmp/tiles/7_42_76.png +0 -0
  159. data/examples/tmp/tiles/7_42_77.png +0 -0
  160. data/examples/tmp/tiles/7_42_78.png +0 -0
  161. data/examples/tmp/tiles/7_42_79.png +0 -0
  162. data/examples/tmp/tiles/7_42_80.png +0 -0
  163. data/examples/tmp/tiles/7_42_81.png +0 -0
  164. data/examples/tmp/tiles/7_42_82.png +0 -0
  165. data/examples/tmp/tiles/7_42_83.png +0 -0
  166. data/examples/tmp/tiles/7_42_84.png +0 -0
  167. data/examples/tmp/tiles/7_42_85.png +0 -0
  168. data/examples/tmp/tiles/7_42_86.png +0 -0
  169. data/examples/tmp/tiles/7_42_87.png +0 -0
  170. data/examples/tmp/tiles/7_43_71.png +0 -0
  171. data/examples/tmp/tiles/7_43_72.png +0 -0
  172. data/examples/tmp/tiles/7_43_73.png +0 -0
  173. data/examples/tmp/tiles/7_43_74.png +0 -0
  174. data/examples/tmp/tiles/7_43_75.png +0 -0
  175. data/examples/tmp/tiles/7_43_76.png +0 -0
  176. data/examples/tmp/tiles/7_43_77.png +0 -0
  177. data/examples/tmp/tiles/7_43_78.png +0 -0
  178. data/examples/tmp/tiles/7_43_79.png +0 -0
  179. data/examples/tmp/tiles/7_43_80.png +0 -0
  180. data/examples/tmp/tiles/7_43_81.png +0 -0
  181. data/examples/tmp/tiles/7_43_82.png +0 -0
  182. data/examples/tmp/tiles/7_43_83.png +0 -0
  183. data/examples/tmp/tiles/7_43_84.png +0 -0
  184. data/examples/tmp/tiles/7_43_85.png +0 -0
  185. data/examples/tmp/tiles/7_43_86.png +0 -0
  186. data/examples/tmp/tiles/7_43_87.png +0 -0
  187. data/examples/tmp/tiles/7_44_71.png +0 -0
  188. data/examples/tmp/tiles/7_44_72.png +0 -0
  189. data/examples/tmp/tiles/7_44_73.png +0 -0
  190. data/examples/tmp/tiles/7_44_74.png +0 -0
  191. data/examples/tmp/tiles/7_44_75.png +0 -0
  192. data/examples/tmp/tiles/7_44_76.png +0 -0
  193. data/examples/tmp/tiles/7_44_77.png +0 -0
  194. data/examples/tmp/tiles/7_44_78.png +0 -0
  195. data/examples/tmp/tiles/7_44_79.png +0 -0
  196. data/examples/tmp/tiles/7_44_80.png +0 -0
  197. data/examples/tmp/tiles/7_44_81.png +0 -0
  198. data/examples/tmp/tiles/7_44_82.png +0 -0
  199. data/examples/tmp/tiles/7_44_83.png +0 -0
  200. data/examples/tmp/tiles/7_44_84.png +0 -0
  201. data/examples/tmp/tiles/7_44_85.png +0 -0
  202. data/examples/tmp/tiles/7_44_86.png +0 -0
  203. data/examples/tmp/tiles/7_44_87.png +0 -0
  204. data/examples/tmp/tiles/7_75_64.png +0 -0
  205. data/examples/tmp/tiles/7_75_65.png +0 -0
  206. data/examples/tmp/tiles/7_75_66.png +0 -0
  207. data/examples/tmp/tiles/7_75_67.png +0 -0
  208. data/examples/tmp/tiles/7_75_68.png +0 -0
  209. data/examples/tmp/tiles/7_76_64.png +0 -0
  210. data/examples/tmp/tiles/7_76_65.png +0 -0
  211. data/examples/tmp/tiles/7_76_66.png +0 -0
  212. data/examples/tmp/tiles/7_76_67.png +0 -0
  213. data/examples/tmp/tiles/7_76_68.png +0 -0
  214. data/examples/tmp/tiles/7_77_64.png +0 -0
  215. data/examples/tmp/tiles/7_77_65.png +0 -0
  216. data/examples/tmp/tiles/7_77_66.png +0 -0
  217. data/examples/tmp/tiles/7_77_67.png +0 -0
  218. data/examples/tmp/tiles/7_77_68.png +0 -0
  219. data/lib/gd/gis/basemap.rb +61 -0
  220. data/lib/gd/gis/layer_points.rb +25 -0
  221. data/lib/gd/gis/map.rb +108 -0
  222. data/lib/gd/gis/projection.rb +25 -0
  223. data/lib/gd/gis.rb +3 -0
  224. data/lib/libgd_gis.rb +44 -0
  225. metadata +281 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5e2c3fe258b7fdb52d1775d50e18095333bf692416c4b4fadf3659febf174604
4
+ data.tar.gz: 3e1ebd506ff4469f231ada7621a5a10a395464d2c373679ee81aacf4e6c1dd35
5
+ SHA512:
6
+ metadata.gz: c9bf3495bb2918619807b7b26a3f26b8cb523e839ec0813b7fdb369f9b26ce78add87f0d322e13acff7c6e265093b7852374482afad538bd90010aba13f5b12e
7
+ data.tar.gz: bd5c40732c0126cc2b69c195cd675f4108d0dfc107da5acd8df00834e61d1d575a8bf1ce4b256a635e8fe80f6b07aafd8cc4fe9a45997805079a6361eb38171d
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # libgd-gis
2
+
3
+ **A geospatial raster engine for Ruby**
4
+
5
+ Render real maps, GeoJSON layers, and geospatial data directly in Ruby using a native raster backend powered by **libgd**.
6
+
7
+ This project brings back something Ruby lost over time:
8
+ the ability to generate **maps, tiles, and geospatial visualizations natively**, without external tools like QGIS, ImageMagick, or Mapbox.
9
+
10
+ ---
11
+
12
+ ## 🌍 Example
13
+
14
+ This map of Tanzania was generated entirely in Ruby from a GeoJSON dataset of hydroelectric power plants:
15
+
16
+ ![Tanzania hydro plants](examples/output/tanzania_hydro.png)
17
+
18
+ No JavaScript.
19
+ No QGIS.
20
+ No Mapbox.
21
+ Just Ruby.
22
+
23
+ ---
24
+
25
+ ## What is this?
26
+
27
+ `libgd-gis` is a **geospatial rendering engine** for Ruby built on top of [`ruby-libgd`](https://github.com/ggerman/ruby-libgd).
28
+
29
+ It allows you to:
30
+
31
+ - Load GeoJSON, CSV, or any dataset with coordinates
32
+ - Fetch real basemap tiles
33
+ - Reproject WGS84 (lat/lon) into Web Mercator
34
+ - Render points, icons, and layers onto a raster map
35
+ - Generate PNG maps or map tiles
36
+
37
+ This is the same type of pipeline used by professional GIS systems — implemented in Ruby.
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ### System dependency
44
+
45
+ `libgd-gis` depends on **libgd**, via `ruby-libgd`.
46
+
47
+ Install libgd first:
48
+
49
+ **Ubuntu / Debian**
50
+ ```
51
+ sudo apt install libgd-dev
52
+ ```
53
+
54
+ **macOS**
55
+ ```
56
+ brew install gd
57
+ ```
58
+
59
+ ---
60
+
61
+ ### Ruby gems
62
+
63
+ ```
64
+ gem install ruby-libgd
65
+ gem install libgd-gis
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Quick Example
71
+
72
+ Render hydroelectric plants from a GeoJSON file:
73
+
74
+ ```ruby
75
+ require "json"
76
+ require "gd/gis"
77
+
78
+ geo = JSON.parse(File.read("hydro_plants.geojson"))
79
+ plants = geo["features"]
80
+
81
+ lons = plants.map { |f| f["geometry"]["coordinates"][0] }
82
+ lats = plants.map { |f| f["geometry"]["coordinates"][1] }
83
+
84
+ bbox = [lons.min, lats.min, lons.max, lats.max]
85
+
86
+ map = GD::GIS::Map.new(
87
+ bbox: bbox,
88
+ zoom: 7,
89
+ basemap: :carto_light
90
+ )
91
+
92
+ map.add_points(
93
+ plants,
94
+ lon: ->(f) { f["geometry"]["coordinates"][0] },
95
+ lat: ->(f) { f["geometry"]["coordinates"][1] },
96
+ icon: "hydro.png"
97
+ )
98
+
99
+ map.render
100
+ map.save("tanzania_hydro.png")
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Features
106
+
107
+ - Real basemap tiles
108
+ - WGS84 → Web Mercator projection
109
+ - GeoJSON point rendering
110
+ - CSV / JSON support
111
+ - Icon-based symbol layers
112
+ - Automatic bounding box fitting
113
+ - Raster output (PNG)
114
+
115
+ ---
116
+
117
+ ## License
118
+
119
+ MIT
120
+
121
+ ---
122
+
123
+ ## Author
124
+
125
+ Germán Silva
126
+ https://github.com/ggerman
127
+ https://rubystacknews.com
Binary file
@@ -0,0 +1,82 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "fileutils"
4
+ require "gd"
5
+
6
+ ZOOM = 7
7
+ TILE_SIZE = 256
8
+
9
+ # Argentina bounding box (WGS84)
10
+ WEST = -73.6
11
+ EAST = -53.6
12
+ NORTH = -21.8
13
+ SOUTH = -55.1
14
+
15
+ def lon2tile(lon, z)
16
+ ((lon + 180.0) / 360.0 * (2 ** z)).floor
17
+ end
18
+
19
+ def lat2tile(lat, z)
20
+ rad = lat * Math::PI / 180
21
+ ((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math::PI) / 2 * (2 ** z)).floor
22
+ end
23
+
24
+ # Compute tile range for Argentina
25
+ x_min = lon2tile(WEST, ZOOM)
26
+ x_max = lon2tile(EAST, ZOOM)
27
+ y_min = lat2tile(NORTH, ZOOM)
28
+ y_max = lat2tile(SOUTH, ZOOM)
29
+
30
+ puts "Argentina tiles at z=#{ZOOM}:"
31
+ puts "x: #{x_min} .. #{x_max}"
32
+ puts "y: #{y_min} .. #{y_max}"
33
+
34
+ COLS = x_max - x_min + 1
35
+ ROWS = y_max - y_min + 1
36
+
37
+ WIDTH = COLS * TILE_SIZE
38
+ HEIGHT = ROWS * TILE_SIZE
39
+
40
+ FileUtils.mkdir_p("tmp/tiles")
41
+ FileUtils.mkdir_p("output")
42
+
43
+ def fetch_tile(z, x, y)
44
+ path = "tmp/tiles/#{z}_#{x}_#{y}.png"
45
+ return path if File.exist?(path)
46
+
47
+ url = URI("https://basemaps.cartocdn.com/light_all/#{z}/#{x}/#{y}.png")
48
+ puts "Downloading #{url}"
49
+
50
+ Net::HTTP.start(url.host, url.port, use_ssl: true) do |http|
51
+ req = Net::HTTP::Get.new(url)
52
+ req["User-Agent"] = "ruby-libgd-gis"
53
+ res = http.request(req)
54
+ raise "Tile failed #{z}/#{x}/#{y}" unless res.code == "200"
55
+ File.binwrite(path, res.body)
56
+ end
57
+
58
+ path
59
+ end
60
+
61
+ puts "Downloading tiles..."
62
+
63
+ (x_min..x_max).each do |x|
64
+ (y_min..y_max).each do |y|
65
+ fetch_tile(ZOOM, x, y)
66
+ end
67
+ end
68
+
69
+ puts "Composing map..."
70
+
71
+ map = GD::Image.new(WIDTH, HEIGHT)
72
+
73
+ (x_min..x_max).each_with_index do |x, cx|
74
+ (y_min..y_max).each_with_index do |y, cy|
75
+ tile = GD::Image.open("tmp/tiles/#{ZOOM}_#{x}_#{y}.png")
76
+ map.copy(tile, cx * TILE_SIZE, cy * TILE_SIZE, 0, 0, TILE_SIZE, TILE_SIZE)
77
+ end
78
+ end
79
+
80
+ map.save("output/argentina.png")
81
+
82
+ puts "Saved output/argentina.png"
@@ -0,0 +1,89 @@
1
+ require "json"
2
+ require "gd"
3
+
4
+ # --------------------------------
5
+ # Input / output
6
+ # --------------------------------
7
+ BASE_MAP = "output/argentina.png"
8
+ DATA = "localidades.json"
9
+ OUTPUT = "output/argentina_localidades.png"
10
+
11
+ # --------------------------------
12
+ # Bounding box de Argentina (WGS84)
13
+ # --------------------------------
14
+ ARG_MIN_LON = -73.6
15
+ ARG_MAX_LON = -53.6
16
+ ARG_MIN_LAT = -55.1
17
+ ARG_MAX_LAT = -21.8
18
+
19
+ R = 6378137.0
20
+
21
+ def mercator_x(lon)
22
+ lon * Math::PI / 180.0 * R
23
+ end
24
+
25
+ def mercator_y(lat)
26
+ Math.log(Math.tan(Math::PI/4 + lat * Math::PI / 360.0)) * R
27
+ end
28
+
29
+ # --------------------------------
30
+ # Cargar mapa base
31
+ # --------------------------------
32
+ img = GD::Image.open(BASE_MAP)
33
+
34
+ width = img.width
35
+ height = img.height
36
+
37
+ flag = GD::Image.open("flag.png")
38
+ flag_w = flag.width
39
+ flag_h = flag.height
40
+
41
+ # --------------------------------
42
+ # Bounding box proyectado
43
+ # --------------------------------
44
+ min_x = mercator_x(ARG_MIN_LON)
45
+ max_x = mercator_x(ARG_MAX_LON)
46
+ min_y = mercator_y(ARG_MIN_LAT)
47
+ max_y = mercator_y(ARG_MAX_LAT)
48
+
49
+ def lonlat_to_pixel(lon, lat, min_x, max_x, min_y, max_y, width, height)
50
+ x = mercator_x(lon)
51
+ y = mercator_y(lat)
52
+
53
+ px = (x - min_x) / (max_x - min_x) * width
54
+ py = height - (y - min_y) / (max_y - min_y) * height
55
+
56
+ [px.to_i, py.to_i]
57
+ end
58
+
59
+ # --------------------------------
60
+ # Cargar localidades
61
+ # --------------------------------
62
+ data = JSON.parse(File.read(DATA))
63
+
64
+ red = GD::Color.rgb(220, 40, 40)
65
+
66
+ puts "Plotting #{data["localidades"].size} localidades..."
67
+
68
+ data["localidades"].each do |loc|
69
+ lon = loc["centroide"]["lon"]
70
+ lat = loc["centroide"]["lat"]
71
+
72
+ x, y = lonlat_to_pixel(lon, lat, min_x, max_x, min_y, max_y, width, height)
73
+
74
+ # dibujar un punto
75
+ img.copy(
76
+ flag,
77
+ x - flag_w / 2,
78
+ y - flag_h / 2,
79
+ 0, 0,
80
+ flag_w,
81
+ flag_h
82
+ )
83
+ end
84
+
85
+ # --------------------------------
86
+ # Guardar resultado
87
+ # --------------------------------
88
+ img.save(OUTPUT)
89
+ puts "Saved #{OUTPUT}"
@@ -0,0 +1,70 @@
1
+ require "csv"
2
+ require "gd"
3
+ require "pry"
4
+
5
+ ZOOM = 7
6
+ TILE_SIZE = 256
7
+
8
+ # Bounding box Argentina (igual que en argentina_tiles.rb)
9
+ WEST = -73.6
10
+ EAST = -53.6
11
+ NORTH = -21.8
12
+ SOUTH = -55.1
13
+
14
+ def lon2tile(lon, z)
15
+ ((lon + 180.0) / 360.0 * (2 ** z)).floor
16
+ end
17
+
18
+ def lat2tile(lat, z)
19
+ rad = lat * Math::PI / 180
20
+ ((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math::PI) / 2 * (2 ** z)).floor
21
+ end
22
+
23
+ def lon2px(lon, z)
24
+ ((lon + 180.0) / 360.0 * (256 * 2**z))
25
+ end
26
+
27
+ def lat2px(lat, z)
28
+ rad = lat * Math::PI / 180
29
+ ((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math::PI) / 2 * (256 * 2**z))
30
+ end
31
+
32
+ # Same tile bbox used to build argentina.png
33
+ x_min = lon2tile(WEST, ZOOM)
34
+ y_min = lat2tile(NORTH, ZOOM)
35
+
36
+ offset_x = x_min * TILE_SIZE
37
+ offset_y = y_min * TILE_SIZE
38
+
39
+ # Load map
40
+ img = GD::Image.open("output/argentina.png")
41
+
42
+ # Load icon
43
+ icon = GD::Image.open("museos.png")
44
+ iw = icon.width
45
+ ih = icon.height
46
+
47
+ puts "Plotting museums..."
48
+
49
+ CSV.foreach("museos_datosabiertos.csv", headers: true, encoding: "bom|utf-8") do |row|
50
+ next if row["Latitud"].nil? || row["Longitud"].nil?
51
+
52
+ lat = row["Latitud"].to_f
53
+ lon = row["Longitud"].to_f
54
+
55
+ # global pixel in Web Mercator
56
+ px = lon2px(lon, ZOOM)
57
+ py = lat2px(lat, ZOOM)
58
+
59
+ # local pixel inside argentina.png
60
+ x = px - offset_x
61
+ y = py - offset_y
62
+
63
+ # skip if outside raster
64
+ next if x < 0 || y < 0 || x >= img.width || y >= img.height
65
+
66
+ img.copy(icon, x.to_i - iw/2, y.to_i - ih/2, 0, 0, iw, ih)
67
+ end
68
+
69
+ img.save("output/argentina_museos.png")
70
+ puts "Saved output/argentina_museos.png"
data/examples/flag.png ADDED
Binary file
Binary file
@@ -0,0 +1,25 @@
1
+ {
2
+ "type": "FeatureCollection",
3
+ "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
4
+ "features": [
5
+ { "type": "Feature", "properties": { "id": 0, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Kagera", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": "T" }, "geometry": { "type": "Point", "coordinates": [ 31.0933382, -1.196415 ] } },
6
+ { "type": "Feature", "properties": { "id": 7, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Nyumba ya Mungu", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 37.4403441, -3.8195803 ] } },
7
+ { "type": "Feature", "properties": { "id": 8, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Chutes Pangani I", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": "T" }, "geometry": { "type": "Point", "coordinates": [ 38.759691784504, -5.343275566027487 ] } },
8
+ { "type": "Feature", "properties": { "id": 9, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Chutes Pangani II", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 38.481225781243062, -5.269153170191239 ] } },
9
+ { "type": "Feature", "properties": { "id": 10, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "New Pangani", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 38.591509346890959, -5.378960936287418 ] } },
10
+ { "type": "Feature", "properties": { "id": 11, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Hale Tanesco", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": "T" }, "geometry": { "type": "Point", "coordinates": [ 38.635622773150118, -5.225224606056146 ] } },
11
+ { "type": "Feature", "properties": { "id": 13, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Mufindi", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": "T" }, "geometry": { "type": "Point", "coordinates": [ 35.5402297, -7.9807668 ] } },
12
+ { "type": "Feature", "properties": { "id": 14, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Mtera", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 35.891170578822909, -7.096171197430225 ] } },
13
+ { "type": "Feature", "properties": { "id": 15, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Kidata", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 36.970001220703125, -7.679999828338623 ] } },
14
+ { "type": "Feature", "properties": { "id": 16, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Bas Kihansi", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": "T" }, "geometry": { "type": "Point", "coordinates": [ 35.8792647, -8.6122779 ] } },
15
+ { "type": "Feature", "properties": { "id": 19, "status": "Existing", "source": "World Bank", "country": "Tanzania", "name": "Ruhudji", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 0, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 34.779586311911132, -9.462585584742733 ] } },
16
+ { "type": "Feature", "properties": { "id": 0, "status": "Proposed", "source": "Tanesco", "country": "Tanzania", "name": "Masigira", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 35.057617146214611, -9.858152893694784 ] } },
17
+ { "type": "Feature", "properties": { "id": 0, "status": "Proposed", "source": "Tanesco", "country": "Tanzania", "name": "Ruhudji", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 35.432706580089054, -9.2930181466573 ] } },
18
+ { "type": "Feature", "properties": { "id": 0, "status": "Proposed", "source": "Tanesco", "country": "Tanzania", "name": "Nakatuta", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 35.472716119702326, -11.15346173867451 ] } },
19
+ { "type": "Feature", "properties": { "id": 0, "status": "Existing", "source": "Tanesco", "country": "Tanzania", "name": "Sunda Falls(MHP)", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 36.89805596842519, -11.413523746160788 ] } },
20
+ { "type": "Feature", "properties": { "id": 0, "status": "Existing", "source": "Tanesco", "country": "Tanzania", "name": "Kihansi", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 35.860183447113322, -8.140235053135704 ] } },
21
+ { "type": "Feature", "properties": { "id": 0, "status": "Existing", "source": "Tanesco", "country": "Tanzania", "name": "Tosamaganga", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 35.385145143948549, -7.423783841805231 ] } },
22
+ { "type": "Feature", "properties": { "id": 0, "status": "Proposed", "source": "Tanesco", "country": "Tanzania", "name": "Mpanga", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 35.752782896995242, -8.727883399619813 ] } },
23
+ { "type": "Feature", "properties": { "id": 0, "status": "Proposed", "source": "Tanesco", "country": "Tanzania", "name": "Rumakali", "type": "Hydro", "capacit_MW": 0, "operator": null, "year": 2015, "difference": null }, "geometry": { "type": "Point", "coordinates": [ 33.832324995558118, -8.942934675041157 ] } }
24
+ ]
25
+ }