libgd-gis 0.2.7.pre.alpha.1 → 0.2.9
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 +4 -4
- data/lib/gd/gis/color_helpers.rb +65 -0
- data/lib/gd/gis/geometry.rb +211 -16
- data/lib/gd/gis/layer_geojson.rb +4 -2
- data/lib/gd/gis/layer_lines.rb +27 -19
- data/lib/gd/gis/layer_points.rb +12 -19
- data/lib/gd/gis/layer_polygons.rb +22 -6
- data/lib/gd/gis/map.rb +236 -122
- data/lib/gd/gis/middleware.rb +89 -0
- data/lib/gd/gis/ontology.rb +5 -1
- data/lib/gd/gis/ontology.yml +3 -242
- data/lib/gd/gis/style.rb +28 -1
- data/lib/gd/gis.rb +1 -1
- metadata +8 -8
- data/lib/gd/gis/input.rb +0 -0
- data/lib/gd/gis/path_sampler.rb +0 -68
- data/lib/gd/gis/style/dark.rb +0 -49
- data/lib/gd/gis/style/light.rb +0 -49
- data/lib/gd/gis/style/solarized.rb +0 -49
data/lib/gd/gis/map.rb
CHANGED
|
@@ -8,55 +8,130 @@ require_relative "layer_polygons"
|
|
|
8
8
|
|
|
9
9
|
module GD
|
|
10
10
|
module GIS
|
|
11
|
+
attr_accessor :debug
|
|
12
|
+
|
|
11
13
|
class Map
|
|
12
14
|
TILE_SIZE = 256
|
|
13
15
|
|
|
14
16
|
attr_reader :image
|
|
17
|
+
attr_reader :layers
|
|
15
18
|
attr_accessor :style
|
|
16
19
|
|
|
17
|
-
def initialize(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
def initialize(
|
|
21
|
+
bbox:,
|
|
22
|
+
zoom:,
|
|
23
|
+
basemap:,
|
|
24
|
+
width: nil,
|
|
25
|
+
height: nil,
|
|
26
|
+
crs: nil,
|
|
27
|
+
fitted_bbox: false
|
|
28
|
+
)
|
|
29
|
+
# --------------------------------------------------
|
|
30
|
+
# 1. Basic input validation
|
|
31
|
+
# --------------------------------------------------
|
|
32
|
+
raise ArgumentError, "bbox must be [min_lng, min_lat, max_lng, max_lat]" unless
|
|
33
|
+
bbox.is_a?(Array) && bbox.size == 4
|
|
34
|
+
|
|
35
|
+
raise ArgumentError, "zoom must be an Integer" unless zoom.is_a?(Integer)
|
|
36
|
+
|
|
37
|
+
if (width && !height) || (!width && height)
|
|
38
|
+
raise ArgumentError, "width and height must be provided together"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@zoom = zoom
|
|
42
|
+
@width = width
|
|
43
|
+
@height = height
|
|
44
|
+
|
|
45
|
+
# --------------------------------------------------
|
|
46
|
+
# 2. CRS normalization (input → WGS84 lon/lat)
|
|
47
|
+
# --------------------------------------------------
|
|
48
|
+
if crs
|
|
49
|
+
normalizer = GD::GIS::CRS::Normalizer.new(crs)
|
|
50
|
+
|
|
51
|
+
min_lng, min_lat = normalizer.normalize(bbox[0], bbox[1])
|
|
52
|
+
max_lng, max_lat = normalizer.normalize(bbox[2], bbox[3])
|
|
53
|
+
|
|
54
|
+
bbox = [min_lng, min_lat, max_lng, max_lat]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# --------------------------------------------------
|
|
58
|
+
# 3. Final bbox (viewport-aware if width/height)
|
|
59
|
+
# --------------------------------------------------
|
|
60
|
+
@bbox =
|
|
61
|
+
if width && height && !fitted_bbox
|
|
62
|
+
GD::GIS::Geometry.viewport_bbox(
|
|
63
|
+
bbox: bbox,
|
|
64
|
+
zoom: zoom,
|
|
65
|
+
width: width,
|
|
66
|
+
height: height
|
|
67
|
+
)
|
|
68
|
+
else
|
|
69
|
+
bbox
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# --------------------------------------------------
|
|
73
|
+
# 4. Basemap (uses FINAL bbox)
|
|
74
|
+
# --------------------------------------------------
|
|
75
|
+
@basemap = GD::GIS::Basemap.new(zoom, @bbox, basemap)
|
|
21
76
|
|
|
22
|
-
#
|
|
77
|
+
# --------------------------------------------------
|
|
78
|
+
# 5. Legacy semantic layers (REQUIRED by render)
|
|
79
|
+
# --------------------------------------------------
|
|
23
80
|
@layers = {
|
|
24
|
-
motorway:
|
|
25
|
-
primary:
|
|
81
|
+
motorway: [],
|
|
82
|
+
primary: [],
|
|
26
83
|
secondary: [],
|
|
27
|
-
street:
|
|
28
|
-
minor:
|
|
29
|
-
rail:
|
|
30
|
-
water:
|
|
31
|
-
park:
|
|
84
|
+
street: [],
|
|
85
|
+
minor: [],
|
|
86
|
+
rail: [],
|
|
87
|
+
water: [],
|
|
88
|
+
park: []
|
|
32
89
|
}
|
|
33
90
|
|
|
34
|
-
#
|
|
91
|
+
# Optional alias (semantic clarity, no behavior change)
|
|
92
|
+
@road_layers = @layers
|
|
93
|
+
|
|
94
|
+
# --------------------------------------------------
|
|
95
|
+
# 6. Overlay layers (generic)
|
|
96
|
+
# --------------------------------------------------
|
|
35
97
|
@points_layers = []
|
|
36
98
|
@lines_layers = []
|
|
37
99
|
@polygons_layers = []
|
|
38
100
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
101
|
+
# --------------------------------------------------
|
|
102
|
+
# 7. Style
|
|
103
|
+
# --------------------------------------------------
|
|
43
104
|
@style = nil
|
|
105
|
+
|
|
106
|
+
@debug = false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def features_by_layer(layer)
|
|
110
|
+
return [] unless @layers[layer]
|
|
111
|
+
|
|
112
|
+
@layers[layer].map do |item|
|
|
113
|
+
item.is_a?(Array) ? item.last : item
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def features
|
|
118
|
+
@layers.values.flatten.map do |item|
|
|
119
|
+
item.is_a?(Array) ? item.last : item
|
|
120
|
+
end
|
|
44
121
|
end
|
|
45
122
|
|
|
46
123
|
# -----------------------------------
|
|
47
|
-
# GeoJSON input (unchanged)
|
|
124
|
+
# GeoJSON input (unchanged behavior)
|
|
48
125
|
# -----------------------------------
|
|
49
|
-
|
|
50
126
|
def add_geojson(path)
|
|
51
127
|
features = LayerGeoJSON.load(path)
|
|
52
128
|
|
|
53
129
|
features.each do |feature|
|
|
54
130
|
case feature.layer
|
|
55
131
|
when :water
|
|
56
|
-
# optional: detect river vs canal from properties
|
|
57
132
|
kind =
|
|
58
133
|
case (feature.properties["objeto"] || feature.properties["waterway"]).to_s.downcase
|
|
59
|
-
when /river|río/
|
|
134
|
+
when /river|río/ then :river
|
|
60
135
|
when /stream|arroyo/ then :stream
|
|
61
136
|
else :minor
|
|
62
137
|
end
|
|
@@ -64,14 +139,47 @@ module GD
|
|
|
64
139
|
@layers[:water] << [kind, feature]
|
|
65
140
|
|
|
66
141
|
when :roads
|
|
67
|
-
# map to style categories if you want later
|
|
68
142
|
@layers[:street] << feature
|
|
69
143
|
|
|
70
144
|
when :parks
|
|
71
145
|
@layers[:park] << feature
|
|
72
146
|
|
|
147
|
+
when :track
|
|
148
|
+
# elegí una:
|
|
149
|
+
@layers[:minor] << feature
|
|
150
|
+
# o @layers[:street] << feature
|
|
73
151
|
else
|
|
74
|
-
|
|
152
|
+
geom_type = feature.geometry["type"]
|
|
153
|
+
|
|
154
|
+
if geom_type == "Point"
|
|
155
|
+
points_style = @style.points or
|
|
156
|
+
raise ArgumentError, "Style error: missing 'points' section"
|
|
157
|
+
|
|
158
|
+
font = points_style[:font] or
|
|
159
|
+
raise ArgumentError, "Style error: points.font is required"
|
|
160
|
+
|
|
161
|
+
size = points_style[:size] or
|
|
162
|
+
raise ArgumentError, "Style error: points.size is required"
|
|
163
|
+
|
|
164
|
+
raw_color = points_style[:color]
|
|
165
|
+
color = @style.normalize_color(raw_color)
|
|
166
|
+
|
|
167
|
+
icon = points_style.key?(:icon_fill) && points_style.key?(:icon_stroke) ? [points_style[:icon_stroke], points_style[:icon_stroke]] : nil
|
|
168
|
+
icon = points_style.key?(:icon) ? points_style[:icon] : nil if icon.nil?
|
|
169
|
+
|
|
170
|
+
@points_layers << GD::GIS::PointsLayer.new(
|
|
171
|
+
[feature],
|
|
172
|
+
lon: ->(f) { f.geometry["coordinates"][0] },
|
|
173
|
+
lat: ->(f) { f.geometry["coordinates"][1] },
|
|
174
|
+
icon: icon,
|
|
175
|
+
label: ->(f) { f.properties["name"] }, # 👈 TEXTO
|
|
176
|
+
font: font,
|
|
177
|
+
size: size,
|
|
178
|
+
color: color
|
|
179
|
+
)
|
|
180
|
+
elsif geom_type == "LineString" || geom_type == "MultiLineString"
|
|
181
|
+
@layers[:minor] << feature
|
|
182
|
+
end
|
|
75
183
|
end
|
|
76
184
|
end
|
|
77
185
|
end
|
|
@@ -79,37 +187,12 @@ module GD
|
|
|
79
187
|
# -----------------------------------
|
|
80
188
|
# Overlay layers
|
|
81
189
|
# -----------------------------------
|
|
82
|
-
|
|
83
190
|
def add_points(data, **opts)
|
|
84
|
-
|
|
85
|
-
@points_layers << layer
|
|
86
|
-
layer
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def add_line(coords, **opts)
|
|
90
|
-
feature = {
|
|
91
|
-
"type" => "Feature",
|
|
92
|
-
"geometry" => {
|
|
93
|
-
"type" => "LineString",
|
|
94
|
-
"coordinates" => coords
|
|
95
|
-
},
|
|
96
|
-
"properties" => {}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
add_lines([feature], **opts)
|
|
191
|
+
@points_layers << GD::GIS::PointsLayer.new(data, **opts)
|
|
100
192
|
end
|
|
101
193
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
"type" => "Feature",
|
|
105
|
-
"geometry" => {
|
|
106
|
-
"type" => "MultiLineString",
|
|
107
|
-
"coordinates" => lines
|
|
108
|
-
},
|
|
109
|
-
"properties" => []
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
add_lines([feature], **opts)
|
|
194
|
+
def add_lines(features, **opts)
|
|
195
|
+
@lines_layers << GD::GIS::LinesLayer.new(features, **opts)
|
|
113
196
|
end
|
|
114
197
|
|
|
115
198
|
def add_polygons(polygons, **opts)
|
|
@@ -117,12 +200,21 @@ module GD
|
|
|
117
200
|
end
|
|
118
201
|
|
|
119
202
|
# -----------------------------------
|
|
120
|
-
# Rendering
|
|
203
|
+
# Rendering (LEGACY, UNCHANGED)
|
|
121
204
|
# -----------------------------------
|
|
122
|
-
|
|
123
205
|
def render
|
|
124
206
|
raise "map.style must be set" unless @style
|
|
125
207
|
|
|
208
|
+
if @width && @height
|
|
209
|
+
render_viewport
|
|
210
|
+
else
|
|
211
|
+
render_tiles
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def render_tiles
|
|
216
|
+
raise "map.style must be set" unless @style
|
|
217
|
+
|
|
126
218
|
tiles, x_min, y_min = @basemap.fetch_tiles
|
|
127
219
|
|
|
128
220
|
xs = tiles.map { |t| t[0] }
|
|
@@ -151,26 +243,89 @@ module GD
|
|
|
151
243
|
)
|
|
152
244
|
end
|
|
153
245
|
|
|
154
|
-
|
|
246
|
+
projection = lambda do |lon, lat|
|
|
155
247
|
x, y = GD::GIS::Projection.lonlat_to_global_px(lon, lat, @zoom)
|
|
156
248
|
[(x - origin_x).round, (y - origin_y).round]
|
|
157
249
|
end
|
|
158
250
|
|
|
159
|
-
# 1️⃣
|
|
251
|
+
# 1️⃣ GeoJSON semantic layers
|
|
160
252
|
@style.order.each do |kind|
|
|
161
|
-
draw_layer(kind,
|
|
253
|
+
draw_layer(kind, projection)
|
|
162
254
|
end
|
|
163
255
|
|
|
164
256
|
# 2️⃣ Generic overlays
|
|
165
|
-
@polygons_layers.each { |l| l.render!(@image,
|
|
166
|
-
@lines_layers.each { |l| l.render!(@image,
|
|
167
|
-
@points_layers.each { |l| l.render!(@image,
|
|
257
|
+
@polygons_layers.each { |l| l.render!(@image, projection) }
|
|
258
|
+
@lines_layers.each { |l| l.render!(@image, projection) }
|
|
259
|
+
@points_layers.each { |l| l.render!(@image, projection) }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def render_viewport
|
|
263
|
+
raise "map.style must be set" unless @style
|
|
264
|
+
|
|
265
|
+
@image = GD::Image.new(@width, @height)
|
|
266
|
+
@image.antialias = false
|
|
267
|
+
|
|
268
|
+
# --------------------------------------------------
|
|
269
|
+
# 1. Compute global pixel bbox
|
|
270
|
+
# --------------------------------------------------
|
|
271
|
+
min_lng, min_lat, max_lng, max_lat = @bbox
|
|
272
|
+
|
|
273
|
+
x1, y1 = GD::GIS::Projection.lonlat_to_global_px(min_lng, max_lat, @zoom)
|
|
274
|
+
x2, y2 = GD::GIS::Projection.lonlat_to_global_px(max_lng, min_lat, @zoom)
|
|
168
275
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
276
|
+
# --------------------------------------------------
|
|
277
|
+
# 2. Fetch tiles
|
|
278
|
+
# --------------------------------------------------
|
|
279
|
+
tiles, = @basemap.fetch_tiles
|
|
172
280
|
|
|
173
|
-
|
|
281
|
+
# --------------------------------------------------
|
|
282
|
+
# 3. Draw tiles clipped to viewport
|
|
283
|
+
# --------------------------------------------------
|
|
284
|
+
tiles.each do |x, y, file|
|
|
285
|
+
tile = GD::Image.open(file)
|
|
286
|
+
|
|
287
|
+
tile_x = x * TILE_SIZE
|
|
288
|
+
tile_y = y * TILE_SIZE
|
|
289
|
+
|
|
290
|
+
dst_x = tile_x - x1
|
|
291
|
+
dst_y = tile_y - y1
|
|
292
|
+
|
|
293
|
+
src_x = [0, -dst_x].max
|
|
294
|
+
src_y = [0, -dst_y].max
|
|
295
|
+
|
|
296
|
+
draw_w = [TILE_SIZE - src_x, @width - dst_x - src_x].min
|
|
297
|
+
draw_h = [TILE_SIZE - src_y, @height - dst_y - src_y].min
|
|
298
|
+
|
|
299
|
+
next if draw_w <= 0 || draw_h <= 0
|
|
300
|
+
|
|
301
|
+
@image.copy(
|
|
302
|
+
tile,
|
|
303
|
+
dst_x + src_x,
|
|
304
|
+
dst_y + src_y,
|
|
305
|
+
src_x,
|
|
306
|
+
src_y,
|
|
307
|
+
draw_w,
|
|
308
|
+
draw_h
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# --------------------------------------------------
|
|
313
|
+
# 4. Projection (viewport version)
|
|
314
|
+
# --------------------------------------------------
|
|
315
|
+
projection = lambda do |lon, lat|
|
|
316
|
+
GD::GIS::Geometry.project(lon, lat, @bbox, @zoom)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# --------------------------------------------------
|
|
320
|
+
# 5. REUSE the same render pipeline
|
|
321
|
+
# --------------------------------------------------
|
|
322
|
+
@style.order.each do |kind|
|
|
323
|
+
draw_layer(kind, projection)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
@polygons_layers.each { |l| l.render!(@image, projection) }
|
|
327
|
+
@lines_layers.each { |l| l.render!(@image, projection) }
|
|
328
|
+
@points_layers.each { |l| l.render!(@image, projection) }
|
|
174
329
|
end
|
|
175
330
|
|
|
176
331
|
def save(path)
|
|
@@ -185,9 +340,12 @@ module GD
|
|
|
185
340
|
case kind
|
|
186
341
|
when :street, :primary, :motorway, :secondary, :minor
|
|
187
342
|
@style.roads[kind]
|
|
188
|
-
when :rail
|
|
189
|
-
|
|
190
|
-
when :
|
|
343
|
+
when :rail
|
|
344
|
+
@style.rails
|
|
345
|
+
when :water
|
|
346
|
+
@style.water
|
|
347
|
+
when :park
|
|
348
|
+
@style.parks
|
|
191
349
|
else
|
|
192
350
|
@style.extra[kind] if @style.respond_to?(:extra)
|
|
193
351
|
end
|
|
@@ -207,77 +365,33 @@ module GD
|
|
|
207
365
|
|
|
208
366
|
if style[:stroke]
|
|
209
367
|
color = GD::Color.rgb(*style[:stroke])
|
|
210
|
-
f.draw(@image, @projection, color, width, :water)
|
|
211
|
-
end
|
|
212
368
|
|
|
369
|
+
color = GD::GIS::ColorHelpers.random_vivid if @debug
|
|
370
|
+
|
|
371
|
+
f.draw(@image, projection, color, width, :water)
|
|
372
|
+
end
|
|
213
373
|
else
|
|
214
374
|
f = item
|
|
215
375
|
geom = f.geometry["type"]
|
|
216
376
|
|
|
217
377
|
if geom == "Polygon" || geom == "MultiPolygon"
|
|
218
|
-
|
|
219
|
-
f.draw(@image, @projection, nil, nil, style)
|
|
378
|
+
f.draw(@image, projection, nil, nil, style)
|
|
220
379
|
else
|
|
221
380
|
if style[:stroke]
|
|
222
381
|
color = GD::Color.rgb(*style[:stroke])
|
|
223
|
-
|
|
224
|
-
|
|
382
|
+
|
|
383
|
+
color = GD::GIS::ColorHelpers.random_vivid if @debug
|
|
384
|
+
|
|
385
|
+
width = style[:stroke_width] ? style[:stroke_width].round : 1
|
|
386
|
+
width = 1 if width < 1
|
|
387
|
+
f.draw(@image, projection, color, width)
|
|
225
388
|
end
|
|
226
389
|
end
|
|
227
390
|
end
|
|
228
391
|
end
|
|
229
392
|
end
|
|
230
393
|
|
|
231
|
-
def clear_dynamic_layers
|
|
232
|
-
@dynamic_points.clear
|
|
233
|
-
@dynamic_lines.clear
|
|
234
|
-
@dynamic_polys.clear
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def add_dynamic_point(data, **opts)
|
|
238
|
-
@dynamic_points << GD::GIS::PointsLayer.new(data, **opts)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def add_dynamic_line(coords, **opts)
|
|
242
|
-
feature = {
|
|
243
|
-
"type" => "Feature",
|
|
244
|
-
"geometry" => {
|
|
245
|
-
"type" => "LineString",
|
|
246
|
-
"coordinates" => coords
|
|
247
|
-
},
|
|
248
|
-
"properties" => {}
|
|
249
|
-
}
|
|
250
|
-
@dynamic_lines << GD::GIS::LinesLayer.new([feature], **opts)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def render_base
|
|
254
|
-
render
|
|
255
|
-
@base_image = @image
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
def render_with_base
|
|
259
|
-
img = GD::Image.new(@base_image.width, @base_image.height)
|
|
260
|
-
img.copy(@base_image, 0,0, 0,0, @base_image.width, @base_image.height)
|
|
261
|
-
|
|
262
|
-
@points_layers.each { |l| l.render!(img, @projection) }
|
|
263
|
-
@lines_layers.each { |l| l.render!(img, @projection) }
|
|
264
|
-
@polygons_layers.each{ |l| l.render!(img, @projection) }
|
|
265
|
-
|
|
266
|
-
img
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
private
|
|
270
|
-
|
|
271
|
-
def add_lines(features, **opts)
|
|
272
|
-
stroke = opts.delete(:color) || opts.delete(:stroke)
|
|
273
|
-
width = opts.delete(:width) || opts.delete(:stroke_width)
|
|
274
|
-
|
|
275
|
-
raise ArgumentError, "missing :color or :stroke" unless stroke
|
|
276
|
-
raise ArgumentError, "missing :width" unless width
|
|
277
|
-
|
|
278
|
-
@lines_layers << GD::GIS::LinesLayer.new(features, :stroke => stroke, :width => width)
|
|
279
|
-
end
|
|
280
|
-
|
|
281
394
|
end
|
|
282
395
|
end
|
|
283
396
|
end
|
|
397
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module GD
|
|
2
|
+
module GIS
|
|
3
|
+
module CRS
|
|
4
|
+
CRS84 = "urn:ogc:def:crs:OGC:1.3:CRS84"
|
|
5
|
+
EPSG4326 = "EPSG:4326"
|
|
6
|
+
EPSG3857 = "EPSG:3857"
|
|
7
|
+
GK_ARGENTINA = "EPSG:22195" # Gauss–Krüger Argentina (zone 5 example)
|
|
8
|
+
|
|
9
|
+
# Normalize any CRS → CRS84 (lon,lat in degrees)
|
|
10
|
+
class Normalizer
|
|
11
|
+
def initialize(crs)
|
|
12
|
+
@crs = normalize_name(crs)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def normalize(lon, lat)
|
|
16
|
+
case @crs
|
|
17
|
+
when CRS84
|
|
18
|
+
[lon, lat]
|
|
19
|
+
|
|
20
|
+
when EPSG4326
|
|
21
|
+
# EPSG:4326 uses (lat, lon)
|
|
22
|
+
[lat, lon]
|
|
23
|
+
|
|
24
|
+
when GK_ARGENTINA
|
|
25
|
+
gk_to_wgs84(lon, lat)
|
|
26
|
+
|
|
27
|
+
when EPSG3857
|
|
28
|
+
mercator_to_wgs84(lon, lat)
|
|
29
|
+
|
|
30
|
+
else
|
|
31
|
+
raise "Unsupported CRS: #{@crs}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def normalize_name(name)
|
|
38
|
+
return CRS84 if name.nil?
|
|
39
|
+
name.to_s.strip
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Web Mercator → WGS84
|
|
43
|
+
def mercator_to_wgs84(x, y)
|
|
44
|
+
r = 6378137.0
|
|
45
|
+
lon = (x / r) * 180.0 / Math::PI
|
|
46
|
+
lat = (2 * Math.atan(Math.exp(y / r)) - Math::PI / 2) * 180.0 / Math::PI
|
|
47
|
+
[lon, lat]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Gauss–Krüger Argentina (Zone 5) → WGS84
|
|
51
|
+
# This is enough precision for mapping
|
|
52
|
+
def gk_to_wgs84(easting, northing)
|
|
53
|
+
# Parameters for Argentina GK Zone 5
|
|
54
|
+
a = 6378137.0
|
|
55
|
+
f = 1 / 298.257223563
|
|
56
|
+
e2 = 2*f - f*f
|
|
57
|
+
lon0 = -60.0 * Math::PI / 180.0 # central meridian zone 5
|
|
58
|
+
|
|
59
|
+
x = easting - 500000.0
|
|
60
|
+
y = northing
|
|
61
|
+
|
|
62
|
+
m = y
|
|
63
|
+
mu = m / (a * (1 - e2/4 - 3*e2*e2/64))
|
|
64
|
+
|
|
65
|
+
e1 = (1 - Math.sqrt(1 - e2)) / (1 + Math.sqrt(1 - e2))
|
|
66
|
+
|
|
67
|
+
j1 = 3*e1/2 - 27*e1**3/32
|
|
68
|
+
j2 = 21*e1**2/16 - 55*e1**4/32
|
|
69
|
+
|
|
70
|
+
fp = mu + j1*Math.sin(2*mu) + j2*Math.sin(4*mu)
|
|
71
|
+
|
|
72
|
+
c1 = e2 * Math.cos(fp)**2
|
|
73
|
+
t1 = Math.tan(fp)**2
|
|
74
|
+
r1 = a * (1 - e2) / (1 - e2 * Math.sin(fp)**2)**1.5
|
|
75
|
+
n1 = a / Math.sqrt(1 - e2 * Math.sin(fp)**2)
|
|
76
|
+
|
|
77
|
+
d = x / n1
|
|
78
|
+
|
|
79
|
+
lat = fp - (n1*Math.tan(fp)/r1) *
|
|
80
|
+
(d**2/2 - (5 + 3*t1 + 10*c1)*d**4/24)
|
|
81
|
+
|
|
82
|
+
lon = lon0 + (d - (1 + 2*t1 + c1)*d**3/6) / Math.cos(fp)
|
|
83
|
+
|
|
84
|
+
[lon * 180.0 / Math::PI, lat * 180.0 / Math::PI]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
data/lib/gd/gis/ontology.rb
CHANGED
|
@@ -8,7 +8,7 @@ module GD
|
|
|
8
8
|
@rules = YAML.load_file(path)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def classify(properties)
|
|
11
|
+
def classify(properties, geometry_type: nil)
|
|
12
12
|
@rules.each do |layer, sources|
|
|
13
13
|
sources.each do |source, rules|
|
|
14
14
|
rules.each do |key, values|
|
|
@@ -19,8 +19,12 @@ module GD
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
return :points if geometry_type == "Point"
|
|
24
|
+
|
|
22
25
|
nil
|
|
23
26
|
end
|
|
27
|
+
|
|
24
28
|
end
|
|
25
29
|
end
|
|
26
30
|
end
|