libgd-gis 0.2.2 → 0.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7afcf6590c4c914f01e441c298e563a483019805ee7221e0410c36d11b2d3dd9
4
- data.tar.gz: ff54803ac49c4b418bbaa1623c016905bfef3a8cff23b541776af9aa579e2985
3
+ metadata.gz: 9112994f7952782be773dc2a7bb8659d96769903b6e9c81b28a563cb2caa80eb
4
+ data.tar.gz: 569e8be0ac09110cf30b3b888317a5079fc975176988ee8051b7f3419587b721
5
5
  SHA512:
6
- metadata.gz: f5fb141edea14c8db4eeded6289230eb4bc003f72830900ab51ad325705720faa48eefc360a1a734ead028868ad7ac4e9de1faf7a620f15975bb2e43abfc780d
7
- data.tar.gz: 413d0d783053d092e6d734ef5859b85c7e45563638a4b2b738caaada4825fcf17a3dd660f291bf327f78c79317c112f34afdc7ed4076b842700a5bbdaf245ff8
6
+ metadata.gz: 85d38d16fdbc475ed5938c32a94819dabbdee7b4d706c369f8a84faec9753bf3765bcd5b4134a47ba62775ead533dad0c42a40cad46085d520f1333c0aacdb1c
7
+ data.tar.gz: ea38df19e492b85550edcebeb1e85157422e619bb7922cf4e49750bd9dc1978afc58e7a6163773de769aabfe60240803f062037c545774bf29f79bbae5324d4a
data/README.md CHANGED
@@ -32,10 +32,11 @@
32
32
 
33
33
  | Examples | Examples | Examples |
34
34
  | :----: | :----: | :--: |
35
+ | <img src="examples/nyc/nyc.png" height="250"> | <img src="examples/tokyo/tokyo.png" height="250"> | <img src="examples/parana/parana.png" height="250"> |
35
36
  | <img src="docs/examples/america.png" height="250"> | <img src="docs/examples/argentina_museum.png" height="250"> | <img src="docs/examples/museos_parana.png" height="250"> |
36
37
  | <img src="docs/examples/asia.png" height="250"> | <img src="docs/examples/europe.png" height="250"> | <img src="docs/examples/icecream_parana.png" height="250"> |
37
38
  | <img src="docs/examples/argentina_cities.png" height="250"> | <img src="docs/examples/tanzania_hydro.png" height="250"> | <img src="docs/examples/parana_polygon.png" height="250"> |
38
- | <img src="docs/examples/parana_carto_dark.png" height="250"> | <img src="docs/examples/ramirez_avenue.png" height="250"> | |
39
+ | <img src="docs/examples/parana_carto_dark.png" height="250"> | <img src="docs/examples/ramirez_avenue.png" height="250"> | <img src="examples/paris/paris.png" height="250"> |
39
40
 
40
41
  ---
41
42
 
@@ -59,7 +59,7 @@ module GD
59
59
  "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/#{z}/#{y}/#{x}"
60
60
 
61
61
  # ==============================
62
- # STAMEN
62
+ # STAMEN 503
63
63
  # ==============================
64
64
  when :stamen_toner
65
65
  "https://stamen-tiles.a.ssl.fastly.net/toner/#{z}/#{x}/#{y}.png"
@@ -80,7 +80,7 @@ module GD
80
80
  "https://a.tile.opentopomap.org/#{z}/#{x}/#{y}.png"
81
81
 
82
82
  # ==============================
83
- # Wikimedia
83
+ # Wikimedia 403
84
84
  # ==============================
85
85
  when :wikimedia
86
86
  "https://maps.wikimedia.org/osm-intl/#{z}/#{x}/#{y}.png"
@@ -0,0 +1,57 @@
1
+ module GD
2
+ module GIS
3
+ class Classifier
4
+ def self.road(feature)
5
+ tags = feature.properties || {}
6
+
7
+ case tags["highway"]
8
+ when "motorway", "trunk"
9
+ :motorway
10
+ when "primary", "primary_link"
11
+ :primary
12
+ when "secondary", "secondary_link"
13
+ :secondary
14
+ when "tertiary"
15
+ :street
16
+ when "residential", "living_street"
17
+ :street
18
+ when "service", "track"
19
+ :minor
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ def self.water?(feature)
26
+ p = feature.properties
27
+
28
+ p["waterway"] ||
29
+ p["natural"] == "water" ||
30
+ p["fclass"] == "river" ||
31
+ p["fclass"] == "stream"
32
+ end
33
+
34
+ def self.rail?(feature)
35
+ tags = feature.properties || {}
36
+ tags["railway"]
37
+ end
38
+
39
+ def self.park?(feature)
40
+ tags = feature.properties || {}
41
+ %w[park recreation_ground garden].include?(tags["leisure"]) ||
42
+ %w[park grass forest].include?(tags["landuse"])
43
+ end
44
+
45
+ def self.water_kind(feature)
46
+ p = feature.properties
47
+
48
+ case p["waterway"] || p["fclass"]
49
+ when "river" then :river
50
+ when "stream" then :stream
51
+ else :minor
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ OUT="all_ruby_sources.txt"
6
+ > "$OUT"
7
+
8
+ echo "### Ruby sources dump ($(date))" >> "$OUT"
9
+ echo >> "$OUT"
10
+
11
+ find . -type f -name "*.rb" | sort | while read -r file; do
12
+ echo "===== FILE: $file =====" >> "$OUT"
13
+ echo >> "$OUT"
14
+ cat "$file" >> "$OUT"
15
+ echo >> "$OUT"
16
+ echo >> "$OUT"
17
+ done
18
+
19
+ echo "Wrote $OUT"
@@ -0,0 +1,152 @@
1
+ module GD
2
+ module GIS
3
+ class Feature
4
+ attr_reader :geometry, :properties
5
+
6
+ def initialize(geometry, properties)
7
+ @geometry = geometry
8
+ @properties = properties || {}
9
+ end
10
+
11
+ # -------------------------------------------------
12
+ # Main draw entry point
13
+ # -------------------------------------------------
14
+ def draw(img, projection, color, width, layer = nil)
15
+ case geometry["type"]
16
+ when "Polygon"
17
+ if layer == :water
18
+ draw_polygon_outline(img, projection, geometry["coordinates"], color, width)
19
+ elsif layer.is_a?(Hash)
20
+ draw_polygon_styled(img, projection, geometry["coordinates"], layer)
21
+ else
22
+ draw_polygon(img, projection, geometry["coordinates"], color)
23
+ end
24
+ when "MultiPolygon"
25
+ geometry["coordinates"].each do |poly|
26
+ if layer == :water
27
+ draw_polygon_outline(img, projection, poly, color, width)
28
+ elsif layer.is_a?(Hash)
29
+ draw_polygon_styled(img, projection, poly, layer)
30
+ else
31
+ draw_polygon(img, projection, poly, color)
32
+ end
33
+ end
34
+ when "LineString", "MultiLineString"
35
+ draw_lines(img, projection, geometry["coordinates"], color, width)
36
+ end
37
+ end
38
+
39
+ # -------------------------------------------------
40
+ # Styled polygon rendering (fill + stroke)
41
+ # -------------------------------------------------
42
+ def draw_polygon_styled(img, projection, rings, style)
43
+ fill = style[:fill] ? GD::Color.rgb(*style[:fill]) : nil
44
+ stroke = style[:stroke] ? GD::Color.rgb(*style[:stroke]) : nil
45
+
46
+ rings.each do |ring|
47
+ pts = ring.map do |lon,lat|
48
+ x,y = projection.call(lon,lat)
49
+ next if x.nil? || y.nil?
50
+ [x.to_i, y.to_i]
51
+ end.compact
52
+
53
+ pts = pts.chunk_while { |a,b| a == b }.map(&:first)
54
+ next if pts.length < 3
55
+
56
+ img.filled_polygon(pts, fill) if fill
57
+
58
+ if stroke
59
+ pts.each_cons(2) { |a,b| img.line(a[0],a[1], b[0],b[1], stroke) }
60
+ img.line(pts.last[0], pts.last[1], pts.first[0], pts.first[1], stroke)
61
+ end
62
+ end
63
+ end
64
+
65
+ # -------------------------------------------------
66
+ # Polygon outline (used for water)
67
+ # -------------------------------------------------
68
+ def draw_polygon_outline(img, projection, rings, color, width)
69
+ return if color.nil?
70
+
71
+ rings.each do |ring|
72
+ pts = ring.map do |lon, lat|
73
+ x, y = projection.call(lon, lat)
74
+ [x.to_i, y.to_i] if x && y
75
+ end.compact
76
+
77
+ next if pts.size < 2
78
+
79
+ img.lines(pts, color, width)
80
+ end
81
+ end
82
+
83
+ # -------------------------------------------------
84
+ # Legacy filled polygon (single color)
85
+ # -------------------------------------------------
86
+ def draw_polygon(img, projection, rings, color)
87
+ return if color.nil?
88
+
89
+ rings.each do |ring|
90
+ pts = ring.map do |lon,lat|
91
+ x,y = projection.call(lon,lat)
92
+ next if x.nil? || y.nil?
93
+ [x.to_i, y.to_i]
94
+ end.compact
95
+
96
+ pts = pts.chunk_while { |a,b| a == b }.map(&:first)
97
+ next if pts.length < 3
98
+
99
+ img.filled_polygon(pts, color)
100
+ end
101
+ end
102
+
103
+ # -------------------------------------------------
104
+ # Lines
105
+ # -------------------------------------------------
106
+ def draw_lines(img, projection, coords, color, width)
107
+ return if color.nil?
108
+
109
+ if coords.first.is_a?(Array) && coords.first.first.is_a?(Array)
110
+ coords.each { |line| draw_line(img, projection, line, color, width) }
111
+ else
112
+ draw_line(img, projection, coords, color, width)
113
+ end
114
+ end
115
+
116
+ def draw_line(img, projection, coords, color, width)
117
+ return if color.nil?
118
+
119
+ coords.each_cons(2) do |(lon1,lat1),(lon2,lat2)|
120
+ x1,y1 = projection.call(lon1,lat1)
121
+ x2,y2 = projection.call(lon2,lat2)
122
+ img.line(x1, y1, x2, y2, color, thickness: width)
123
+ end
124
+ end
125
+
126
+ # -------------------------------------------------
127
+ # Metadata helpers
128
+ # -------------------------------------------------
129
+ def label
130
+ properties["name:ja"] || properties["name"]
131
+ end
132
+
133
+ def centroid
134
+ pts = []
135
+
136
+ case geometry["type"]
137
+ when "LineString"
138
+ pts = geometry["coordinates"]
139
+ when "MultiLineString"
140
+ pts = geometry["coordinates"].flatten(1)
141
+ end
142
+
143
+ return nil if pts.empty?
144
+
145
+ lon = pts.map(&:first).sum / pts.size
146
+ lat = pts.map(&:last).sum / pts.size
147
+
148
+ [lon, lat]
149
+ end
150
+ end
151
+ end
152
+ end
@@ -1,76 +1,16 @@
1
1
  require "json"
2
+ require_relative "feature"
2
3
 
3
4
  module GD
4
5
  module GIS
5
6
  class LayerGeoJSON
6
- def initialize(path, options = {})
7
- @geojson = JSON.parse(File.read(path))
8
- @color = options[:color]
9
- @icon = options[:icon]
10
- @label = options[:label]
11
- @font = options[:font]
12
- @size = options[:size] || 10
13
- end
14
-
15
- def render!(img, projection)
16
- features = @geojson["features"] || []
17
-
18
- features.each do |f|
19
- geom = f["geometry"]
20
- next unless geom
21
-
22
- type = geom["type"]
23
- coords = geom["coordinates"]
24
-
25
- case type
26
- when "Point"
27
- draw_points(img, projection, [coords], f["properties"])
28
- when "MultiPoint"
29
- draw_points(img, projection, coords, f["properties"])
30
- when "LineString"
31
- draw_lines(img, projection, [coords])
32
- when "MultiLineString"
33
- coords.each { |l| draw_lines(img, projection, [l]) }
34
- when "Polygon"
35
- draw_polygons(img, projection, coords)
36
- when "MultiPolygon"
37
- coords.each { |p| draw_polygons(img, projection, p) }
38
- end
39
- end
40
- end
41
-
42
- private
43
-
44
- def draw_lines(img, projection, lines)
45
- color = @color || [120,120,120]
46
-
47
- lines.each do |line|
48
- pts = line.map { |p| projection.call(p[0], p[1]) }
49
-
50
- pts.each_cons(2) do |(x1, y1), (x2, y2)|
51
- img.line(x1, y1, x2, y2, color)
52
- end
53
- end
54
- end
55
-
56
- def draw_polygons(img, projection, rings)
57
- fill = @color || [200,200,200]
58
- stroke = @color || [120,120,120]
59
-
60
- rings.each do |ring|
61
- pts = ring.map { |p| projection.call(p[0], p[1]) }
62
-
63
- # fill
64
- img.polygon(pts, fill)
65
-
66
- # stroke
67
- pts.each_cons(2) do |(x1, y1), (x2, y2)|
68
- img.line(x1, y1, x2, y2, stroke)
7
+ def self.load(path)
8
+ data = JSON.parse(File.read(path))
9
+ features = data["features"]
10
+ features.map do |f|
11
+ Feature.new(f["geometry"], f["properties"])
69
12
  end
70
13
  end
71
14
  end
72
-
73
-
74
- end
75
15
  end
76
16
  end
data/lib/gd/gis/map.rb CHANGED
@@ -1,58 +1,91 @@
1
- require "gd"
2
1
  require_relative "basemap"
3
2
  require_relative "projection"
4
- require_relative "geometry"
3
+ require_relative "classifier"
4
+ require_relative "layer_geojson"
5
5
  require_relative "layer_points"
6
6
  require_relative "layer_lines"
7
7
  require_relative "layer_polygons"
8
- require_relative "layer_geojson"
9
8
 
10
9
  module GD
11
10
  module GIS
12
11
  class Map
13
12
  TILE_SIZE = 256
14
13
 
14
+ attr_reader :image
15
+ attr_accessor :style
16
+
15
17
  def initialize(bbox:, zoom:, basemap:)
16
- @bbox = bbox
17
- @zoom = zoom
18
- @basemap = Basemap.new(zoom, bbox, basemap)
19
- @layers = []
18
+ @bbox = bbox
19
+ @zoom = zoom
20
+ @basemap = Basemap.new(zoom, bbox, basemap)
21
+
22
+ # 🔒 DO NOT CHANGE — this is the working GeoJSON pipeline
23
+ @layers = {
24
+ motorway: [],
25
+ primary: [],
26
+ secondary: [],
27
+ street: [],
28
+ minor: [],
29
+ rail: [],
30
+ water: [],
31
+ park: []
32
+ }
33
+
34
+ # 🆕 overlay layers
35
+ @points_layers = []
36
+ @lines_layers = []
37
+ @polygons_layers = []
38
+
39
+ @style = nil
20
40
  end
21
41
 
22
- # ------------------------------
23
- # Layer DSL
24
- # ------------------------------
25
-
26
- def add_points(data, lon:, lat:, icon: nil, label: nil, font: nil, size: 12, color: [0,0,0])
27
- @layers << PointsLayer.new(
28
- data,
29
- lon: lon,
30
- lat: lat,
31
- icon: icon,
32
- label: label,
33
- font: font,
34
- size: size,
35
- color: color
36
- )
42
+ # -----------------------------------
43
+ # GeoJSON input (unchanged)
44
+ # -----------------------------------
45
+
46
+ def add_geojson(path)
47
+ features = LayerGeoJSON.load(path)
48
+
49
+ features.each do |feature|
50
+ if Classifier.water?(feature)
51
+ kind = Classifier.water_kind(feature)
52
+ @layers[:water] << [kind, feature]
53
+
54
+ elsif Classifier.park?(feature)
55
+ @layers[:park] << feature
56
+
57
+ elsif Classifier.rail?(feature)
58
+ @layers[:rail] << feature
59
+
60
+ elsif type = Classifier.road(feature)
61
+ @layers[type] << feature
62
+ end
63
+ end
37
64
  end
38
65
 
39
- def add_lines(features, stroke:, width:)
40
- @layers << LinesLayer.new(features, stroke: stroke, width: width)
66
+ # -----------------------------------
67
+ # Overlay layers
68
+ # -----------------------------------
69
+
70
+ def add_points(data, **opts)
71
+ @points_layers << GD::GIS::PointsLayer.new(data, **opts)
41
72
  end
42
73
 
43
- def add_polygons(polygons, fill:, stroke: nil, width: nil)
44
- @layers << PolygonsLayer.new(polygons, fill: fill, stroke: stroke, width: width)
74
+ def add_lines(features, **opts)
75
+ @lines_layers << GD::GIS::LinesLayer.new(features, **opts)
45
76
  end
46
77
 
47
- def add_geojson(path, **options)
48
- @layers << LayerGeoJSON.new(path, options)
78
+ def add_polygons(polygons, **opts)
79
+ @polygons_layers << GD::GIS::PolygonsLayer.new(polygons, **opts)
49
80
  end
50
81
 
51
- # ------------------------------
82
+ # -----------------------------------
52
83
  # Rendering
53
- # ------------------------------
84
+ # -----------------------------------
85
+
86
+ def render
87
+ raise "map.style must be set" unless @style
54
88
 
55
- def render(path = "map.png")
56
89
  tiles, x_min, y_min = @basemap.fetch_tiles
57
90
 
58
91
  xs = tiles.map { |t| t[0] }
@@ -67,42 +100,92 @@ module GD
67
100
  origin_x = x_min * TILE_SIZE
68
101
  origin_y = y_min * TILE_SIZE
69
102
 
70
- @img = GD::Image.new(width, height)
71
- @img.alpha_blending = true
72
- @img.save_alpha = true
103
+ @image = GD::Image.new(width, height)
104
+ @image.antialias(false)
73
105
 
74
- # Draw basemap
106
+ # Basemap
75
107
  tiles.each do |x, y, file|
76
108
  tile = GD::Image.open(file)
77
- cx = x - x_min
78
- cy = y - y_min
79
- @img.copy(tile, cx * TILE_SIZE, cy * TILE_SIZE, 0, 0, TILE_SIZE, TILE_SIZE)
109
+ @image.copy(
110
+ tile,
111
+ (x - x_min) * TILE_SIZE,
112
+ (y - y_min) * TILE_SIZE,
113
+ 0, 0, TILE_SIZE, TILE_SIZE
114
+ )
80
115
  end
81
116
 
82
- # WebMercator projection → local pixels
83
- scale = TILE_SIZE * (2 ** @zoom)
84
-
85
117
  projection = lambda do |lon, lat|
86
- x = (lon + 180.0) / 360.0 * scale
87
-
88
- lat_rad = lat * Math::PI / 180.0
89
- y = (1.0 - Math.log(Math.tan(lat_rad) + 1.0 / Math.cos(lat_rad)) / Math::PI) / 2.0 * scale
90
-
91
- [
92
- (x - origin_x).round,
93
- (y - origin_y).round
94
- ]
118
+ x, y = GD::GIS::Projection.lonlat_to_global_px(lon, lat, @zoom)
119
+ [(x - origin_x).round, (y - origin_y).round]
95
120
  end
96
121
 
97
- # Draw layers
98
- @layers.each do |layer|
99
- layer.render!(@img, projection)
122
+ # 1️⃣ Semantic GeoJSON layers (this is what was working)
123
+ @style.order.each do |kind|
124
+ draw_layer(kind, projection)
100
125
  end
126
+
127
+ # 2️⃣ Generic overlays
128
+ @polygons_layers.each { |l| l.render!(@image, projection) }
129
+ @lines_layers.each { |l| l.render!(@image, projection) }
130
+ @points_layers.each { |l| l.render!(@image, projection) }
101
131
  end
102
132
 
103
133
  def save(path)
104
- @img.save(path)
134
+ @image.save(path)
105
135
  end
136
+
137
+ def draw_layer(kind, projection)
138
+ items = @layers[kind]
139
+ return if items.nil? || items.empty?
140
+
141
+ style =
142
+ case kind
143
+ when :street, :primary, :motorway, :secondary, :minor
144
+ @style.roads[kind]
145
+ when :rail then @style.rails
146
+ when :water then @style.water
147
+ when :park then @style.parks
148
+ else
149
+ @style.extra[kind] if @style.respond_to?(:extra)
150
+ end
151
+
152
+ return if style.nil?
153
+
154
+ items.each do |item|
155
+ if kind == :water
156
+ water_kind, f = item
157
+
158
+ width =
159
+ case water_kind
160
+ when :river then 2.5
161
+ when :stream then 1.5
162
+ else 1
163
+ end
164
+
165
+ if style[:stroke]
166
+ color = GD::Color.rgb(*style[:stroke])
167
+ f.draw(@image, projection, color, width, :water)
168
+ end
169
+
170
+ else
171
+ f = item
172
+ geom = f.geometry["type"]
173
+
174
+ if geom == "Polygon" || geom == "MultiPolygon"
175
+ # THIS is the critical fix
176
+ f.draw(@image, projection, nil, nil, style)
177
+ else
178
+ if style[:stroke]
179
+ color = GD::Color.rgb(*style[:stroke])
180
+ width = style[:stroke_width] || 1
181
+ f.draw(@image, projection, color, width)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+
106
189
  end
107
190
  end
108
191
  end
@@ -0,0 +1,49 @@
1
+ module GD
2
+ module GIS
3
+ class Style
4
+ DARK = Style.new(
5
+ roads: {
6
+ motorway: {
7
+ stroke: [255,255,255],
8
+ stroke_width: 10,
9
+ fill: [60,60,60],
10
+ fill_width: 6
11
+ },
12
+ primary: {
13
+ stroke: [200,200,200],
14
+ stroke_width: 7,
15
+ fill: [80,80,80],
16
+ fill_width: 4
17
+ },
18
+ street: {
19
+ stroke: [120,120,120],
20
+ stroke_width: 1
21
+ }
22
+ },
23
+ rail: {
24
+ stroke: [255,255,255],
25
+ stroke_width: 6,
26
+ fill: [220,50,50],
27
+ fill_width: 4,
28
+ center: [255,255,255],
29
+ center_width: 1
30
+ },
31
+ water: {
32
+ fill: [40,80,120],
33
+ stroke: [100,160,220]
34
+ },
35
+ park: {
36
+ fill: [40,80,40]
37
+ },
38
+ order: [
39
+ :water,
40
+ :park,
41
+ :street,
42
+ :primary,
43
+ :motorway,
44
+ :rail
45
+ ]
46
+ )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ module GD
2
+ module GIS
3
+ class Style
4
+ LIGHT = Style.new(
5
+ roads: {
6
+ motorway: {
7
+ stroke: [100,100,100],
8
+ stroke_width: 10,
9
+ fill: [245,245,245],
10
+ fill_width: 6
11
+ },
12
+ primary: {
13
+ stroke: [140,140,140],
14
+ stroke_width: 7,
15
+ fill: [240,240,240],
16
+ fill_width: 4
17
+ },
18
+ street: {
19
+ stroke: [220,220,220],
20
+ stroke_width: 1
21
+ }
22
+ },
23
+ rail: {
24
+ stroke: [80,80,80],
25
+ stroke_width: 6,
26
+ fill: [230,70,70],
27
+ fill_width: 4,
28
+ center: [255,255,255],
29
+ center_width: 1
30
+ },
31
+ water: {
32
+ fill: [168,208,255],
33
+ stroke: [120,180,240]
34
+ },
35
+ park: {
36
+ fill: [205,238,203]
37
+ },
38
+ order: [
39
+ :water,
40
+ :park,
41
+ :street,
42
+ :primary,
43
+ :motorway,
44
+ :rail
45
+ ]
46
+ )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ module GD
2
+ module GIS
3
+ class Style
4
+ SOLARIZED = Style.new(
5
+ roads: {
6
+ motorway: {
7
+ stroke: [7, 54, 66], # Base02
8
+ stroke_width: 10,
9
+ fill: [131, 148, 150], # Base0
10
+ fill_width: 6
11
+ },
12
+ primary: {
13
+ stroke: [88, 110, 117], # Base01
14
+ stroke_width: 7,
15
+ fill: [147, 161, 161], # Base1
16
+ fill_width: 4
17
+ },
18
+ street: {
19
+ stroke: [147, 161, 161], # Base1
20
+ stroke_width: 1
21
+ }
22
+ },
23
+ rails: {
24
+ stroke: [203, 75, 22], # Orange
25
+ stroke_width: 6,
26
+ fill: [220, 50, 47], # Red
27
+ fill_width: 4,
28
+ center: [253, 246, 227], # Base3
29
+ center_width: 1
30
+ },
31
+ water: {
32
+ fill: [38, 139, 210], # Blue
33
+ stroke: [42, 161, 152] # Cyan
34
+ },
35
+ parks: {
36
+ fill: [133, 153, 0] # Green
37
+ },
38
+ order: [
39
+ :water,
40
+ :park,
41
+ :street,
42
+ :primary,
43
+ :motorway,
44
+ :rail
45
+ ]
46
+ )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ require "yaml"
2
+
3
+ module GD
4
+ module GIS
5
+ class Style
6
+ attr_reader :roads, :rails, :water, :parks, :order
7
+
8
+ def initialize(definition)
9
+ @roads = definition[:roads] || {}
10
+ @rails = definition[:rails] || {}
11
+ @water = definition[:water] || {}
12
+ @parks = definition[:parks] || {}
13
+ @order = definition[:order] || []
14
+ end
15
+
16
+ def self.load(name, from: "styles")
17
+ path = File.join(from, "#{name}.yml")
18
+ raise "Style not found: #{path}" unless File.exist?(path)
19
+
20
+ data = YAML.load_file(path)
21
+ data = deep_symbolize(data)
22
+
23
+ new(
24
+ roads: data[:roads],
25
+ rails: data[:rail] || data[:rails],
26
+ water: data[:water],
27
+ parks: data[:park] || data[:parks],
28
+ order: (data[:order] || []).map(&:to_sym)
29
+ )
30
+ end
31
+
32
+ def self.deep_symbolize(obj)
33
+ case obj
34
+ when Hash
35
+ obj.transform_keys(&:to_sym)
36
+ .transform_values { |v| deep_symbolize(v) }
37
+ when Array
38
+ obj.map { |v| deep_symbolize(v) }
39
+ else
40
+ obj
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/gd/gis.rb CHANGED
@@ -1,3 +1,14 @@
1
- # lib/gd/gis.rb
2
1
  require "gd"
2
+
3
+ require_relative "gis/style"
4
+ require_relative "gis/classifier"
5
+
6
+ require_relative "gis/feature"
3
7
  require_relative "gis/map"
8
+ require_relative "gis/basemap"
9
+ require_relative "gis/projection"
10
+ require_relative "gis/geometry"
11
+ require_relative "gis/layer_points"
12
+ require_relative "gis/layer_lines"
13
+ require_relative "gis/layer_polygons"
14
+ require_relative "gis/layer_geojson"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libgd-gis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Germán Alberto Giménez Silva
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-01-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: ruby-libgd
@@ -16,20 +15,20 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: 0.1.9
18
+ version: 0.2.1
20
19
  - - ">="
21
20
  - !ruby/object:Gem::Version
22
- version: 0.1.9
21
+ version: 0.2.1
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
26
25
  requirements:
27
26
  - - "~>"
28
27
  - !ruby/object:Gem::Version
29
- version: 0.1.9
28
+ version: 0.2.1
30
29
  - - ">="
31
30
  - !ruby/object:Gem::Version
32
- version: 0.1.9
31
+ version: 0.2.1
33
32
  description: A native GIS raster engine for Ruby built on libgd. Render maps, GeoJSON,
34
33
  heatmaps and tiles.
35
34
  email:
@@ -41,6 +40,9 @@ files:
41
40
  - README.md
42
41
  - lib/gd/gis.rb
43
42
  - lib/gd/gis/basemap.rb
43
+ - lib/gd/gis/classifier.rb
44
+ - lib/gd/gis/dump.sh
45
+ - lib/gd/gis/feature.rb
44
46
  - lib/gd/gis/geometry.rb
45
47
  - lib/gd/gis/layer_geojson.rb
46
48
  - lib/gd/gis/layer_lines.rb
@@ -48,12 +50,15 @@ files:
48
50
  - lib/gd/gis/layer_polygons.rb
49
51
  - lib/gd/gis/map.rb
50
52
  - lib/gd/gis/projection.rb
53
+ - lib/gd/gis/style.rb
54
+ - lib/gd/gis/style/dark.rb
55
+ - lib/gd/gis/style/light.rb
56
+ - lib/gd/gis/style/solarized.rb
51
57
  - lib/libgd_gis.rb
52
58
  homepage: https://github.com/ggerman/libgd-gis
53
59
  licenses:
54
60
  - MIT
55
61
  metadata: {}
56
- post_install_message:
57
62
  rdoc_options: []
58
63
  require_paths:
59
64
  - lib
@@ -68,8 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
73
  - !ruby/object:Gem::Version
69
74
  version: '0'
70
75
  requirements: []
71
- rubygems_version: 3.5.22
72
- signing_key:
76
+ rubygems_version: 4.0.3
73
77
  specification_version: 4
74
78
  summary: Geospatial raster rendering for Ruby using libgd
75
79
  test_files: []