libgd-gis 0.2.7.pre.alpha.1 → 0.2.8
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_lines.rb +27 -19
- data/lib/gd/gis/layer_points.rb +2 -8
- data/lib/gd/gis/layer_polygons.rb +22 -6
- data/lib/gd/gis/map.rb +209 -122
- data/lib/gd/gis/middleware.rb +89 -0
- data/lib/gd/gis/ontology.yml +3 -242
- 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,20 @@ 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
|
+
if geom_type == "LineString" || geom_type == "MultiLineString"
|
|
154
|
+
@layers[:minor] << feature
|
|
155
|
+
end
|
|
75
156
|
end
|
|
76
157
|
end
|
|
77
158
|
end
|
|
@@ -79,37 +160,12 @@ module GD
|
|
|
79
160
|
# -----------------------------------
|
|
80
161
|
# Overlay layers
|
|
81
162
|
# -----------------------------------
|
|
82
|
-
|
|
83
163
|
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)
|
|
164
|
+
@points_layers << GD::GIS::PointsLayer.new(data, **opts)
|
|
100
165
|
end
|
|
101
166
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
"type" => "Feature",
|
|
105
|
-
"geometry" => {
|
|
106
|
-
"type" => "MultiLineString",
|
|
107
|
-
"coordinates" => lines
|
|
108
|
-
},
|
|
109
|
-
"properties" => []
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
add_lines([feature], **opts)
|
|
167
|
+
def add_lines(features, **opts)
|
|
168
|
+
@lines_layers << GD::GIS::LinesLayer.new(features, **opts)
|
|
113
169
|
end
|
|
114
170
|
|
|
115
171
|
def add_polygons(polygons, **opts)
|
|
@@ -117,12 +173,21 @@ module GD
|
|
|
117
173
|
end
|
|
118
174
|
|
|
119
175
|
# -----------------------------------
|
|
120
|
-
# Rendering
|
|
176
|
+
# Rendering (LEGACY, UNCHANGED)
|
|
121
177
|
# -----------------------------------
|
|
122
|
-
|
|
123
178
|
def render
|
|
124
179
|
raise "map.style must be set" unless @style
|
|
125
180
|
|
|
181
|
+
if @width && @height
|
|
182
|
+
render_viewport
|
|
183
|
+
else
|
|
184
|
+
render_tiles
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def render_tiles
|
|
189
|
+
raise "map.style must be set" unless @style
|
|
190
|
+
|
|
126
191
|
tiles, x_min, y_min = @basemap.fetch_tiles
|
|
127
192
|
|
|
128
193
|
xs = tiles.map { |t| t[0] }
|
|
@@ -151,26 +216,89 @@ module GD
|
|
|
151
216
|
)
|
|
152
217
|
end
|
|
153
218
|
|
|
154
|
-
|
|
219
|
+
projection = lambda do |lon, lat|
|
|
155
220
|
x, y = GD::GIS::Projection.lonlat_to_global_px(lon, lat, @zoom)
|
|
156
221
|
[(x - origin_x).round, (y - origin_y).round]
|
|
157
222
|
end
|
|
158
223
|
|
|
159
|
-
# 1️⃣
|
|
224
|
+
# 1️⃣ GeoJSON semantic layers
|
|
160
225
|
@style.order.each do |kind|
|
|
161
|
-
draw_layer(kind,
|
|
226
|
+
draw_layer(kind, projection)
|
|
162
227
|
end
|
|
163
228
|
|
|
164
229
|
# 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,
|
|
230
|
+
@polygons_layers.each { |l| l.render!(@image, projection) }
|
|
231
|
+
@lines_layers.each { |l| l.render!(@image, projection) }
|
|
232
|
+
@points_layers.each { |l| l.render!(@image, projection) }
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def render_viewport
|
|
236
|
+
raise "map.style must be set" unless @style
|
|
237
|
+
|
|
238
|
+
@image = GD::Image.new(@width, @height)
|
|
239
|
+
@image.antialias = false
|
|
240
|
+
|
|
241
|
+
# --------------------------------------------------
|
|
242
|
+
# 1. Compute global pixel bbox
|
|
243
|
+
# --------------------------------------------------
|
|
244
|
+
min_lng, min_lat, max_lng, max_lat = @bbox
|
|
245
|
+
|
|
246
|
+
x1, y1 = GD::GIS::Projection.lonlat_to_global_px(min_lng, max_lat, @zoom)
|
|
247
|
+
x2, y2 = GD::GIS::Projection.lonlat_to_global_px(max_lng, min_lat, @zoom)
|
|
168
248
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
249
|
+
# --------------------------------------------------
|
|
250
|
+
# 2. Fetch tiles
|
|
251
|
+
# --------------------------------------------------
|
|
252
|
+
tiles, = @basemap.fetch_tiles
|
|
172
253
|
|
|
173
|
-
|
|
254
|
+
# --------------------------------------------------
|
|
255
|
+
# 3. Draw tiles clipped to viewport
|
|
256
|
+
# --------------------------------------------------
|
|
257
|
+
tiles.each do |x, y, file|
|
|
258
|
+
tile = GD::Image.open(file)
|
|
259
|
+
|
|
260
|
+
tile_x = x * TILE_SIZE
|
|
261
|
+
tile_y = y * TILE_SIZE
|
|
262
|
+
|
|
263
|
+
dst_x = tile_x - x1
|
|
264
|
+
dst_y = tile_y - y1
|
|
265
|
+
|
|
266
|
+
src_x = [0, -dst_x].max
|
|
267
|
+
src_y = [0, -dst_y].max
|
|
268
|
+
|
|
269
|
+
draw_w = [TILE_SIZE - src_x, @width - dst_x - src_x].min
|
|
270
|
+
draw_h = [TILE_SIZE - src_y, @height - dst_y - src_y].min
|
|
271
|
+
|
|
272
|
+
next if draw_w <= 0 || draw_h <= 0
|
|
273
|
+
|
|
274
|
+
@image.copy(
|
|
275
|
+
tile,
|
|
276
|
+
dst_x + src_x,
|
|
277
|
+
dst_y + src_y,
|
|
278
|
+
src_x,
|
|
279
|
+
src_y,
|
|
280
|
+
draw_w,
|
|
281
|
+
draw_h
|
|
282
|
+
)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# --------------------------------------------------
|
|
286
|
+
# 4. Projection (viewport version)
|
|
287
|
+
# --------------------------------------------------
|
|
288
|
+
projection = lambda do |lon, lat|
|
|
289
|
+
GD::GIS::Geometry.project(lon, lat, @bbox, @zoom)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# --------------------------------------------------
|
|
293
|
+
# 5. REUSE the same render pipeline
|
|
294
|
+
# --------------------------------------------------
|
|
295
|
+
@style.order.each do |kind|
|
|
296
|
+
draw_layer(kind, projection)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
@polygons_layers.each { |l| l.render!(@image, projection) }
|
|
300
|
+
@lines_layers.each { |l| l.render!(@image, projection) }
|
|
301
|
+
@points_layers.each { |l| l.render!(@image, projection) }
|
|
174
302
|
end
|
|
175
303
|
|
|
176
304
|
def save(path)
|
|
@@ -185,9 +313,12 @@ module GD
|
|
|
185
313
|
case kind
|
|
186
314
|
when :street, :primary, :motorway, :secondary, :minor
|
|
187
315
|
@style.roads[kind]
|
|
188
|
-
when :rail
|
|
189
|
-
|
|
190
|
-
when :
|
|
316
|
+
when :rail
|
|
317
|
+
@style.rails
|
|
318
|
+
when :water
|
|
319
|
+
@style.water
|
|
320
|
+
when :park
|
|
321
|
+
@style.parks
|
|
191
322
|
else
|
|
192
323
|
@style.extra[kind] if @style.respond_to?(:extra)
|
|
193
324
|
end
|
|
@@ -207,77 +338,33 @@ module GD
|
|
|
207
338
|
|
|
208
339
|
if style[:stroke]
|
|
209
340
|
color = GD::Color.rgb(*style[:stroke])
|
|
210
|
-
f.draw(@image, @projection, color, width, :water)
|
|
211
|
-
end
|
|
212
341
|
|
|
342
|
+
color = GD::GIS::ColorHelpers.random_vivid if @debug
|
|
343
|
+
|
|
344
|
+
f.draw(@image, projection, color, width, :water)
|
|
345
|
+
end
|
|
213
346
|
else
|
|
214
347
|
f = item
|
|
215
348
|
geom = f.geometry["type"]
|
|
216
349
|
|
|
217
350
|
if geom == "Polygon" || geom == "MultiPolygon"
|
|
218
|
-
|
|
219
|
-
f.draw(@image, @projection, nil, nil, style)
|
|
351
|
+
f.draw(@image, projection, nil, nil, style)
|
|
220
352
|
else
|
|
221
353
|
if style[:stroke]
|
|
222
354
|
color = GD::Color.rgb(*style[:stroke])
|
|
223
|
-
|
|
224
|
-
|
|
355
|
+
|
|
356
|
+
color = GD::GIS::ColorHelpers.random_vivid if @debug
|
|
357
|
+
|
|
358
|
+
width = style[:stroke_width] ? style[:stroke_width].round : 1
|
|
359
|
+
width = 1 if width < 1
|
|
360
|
+
f.draw(@image, projection, color, width)
|
|
225
361
|
end
|
|
226
362
|
end
|
|
227
363
|
end
|
|
228
364
|
end
|
|
229
365
|
end
|
|
230
366
|
|
|
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
367
|
end
|
|
282
368
|
end
|
|
283
369
|
end
|
|
370
|
+
|
|
@@ -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
|