mapknitter-exporter 0.0.2

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
+ SHA1:
3
+ metadata.gz: f6396f65106739795c21f41cdbf09f5b83f304ba
4
+ data.tar.gz: 5e376ff010e5e8de275f189ede66ec06fcbadd1e
5
+ SHA512:
6
+ metadata.gz: d68a28de27755f50174dd36584a2d3a4e6f20f2b79bbd2ac4e788a36157ea6ba7f1d2d0161fa7fad26ec8848af2dfe275e7340047de788e2dd214c2b80ce9e15
7
+ data.tar.gz: 0c5c5172b613dae3e347aa47b6644d6bfe0fe133ec839dccf97cab71fcbec97ab6bfe69fed0daebc6858d21c003671e0e4c0f72f05052a095ddb064f4409d01b
data/lib/cartagen.rb ADDED
@@ -0,0 +1,67 @@
1
+ class Cartagen
2
+
3
+ def self.chop_word(string)
4
+ word = string.split(' ')[0]
5
+ string.slice!(word+' ')
6
+ word
7
+ end
8
+
9
+ def self.spherical_mercator_lon_to_x(lon,scale=10000)
10
+ (lon*scale)/180
11
+ end
12
+
13
+ def self.spherical_mercator_x_to_lon(x,scale=10000)
14
+ (x/scale)*180
15
+ end
16
+
17
+ def self.spherical_mercator_lat_to_y(lat,scale=10000)
18
+ #180/Math::PI * Math.log(Math.tan(Math::PI/4+lat*(Math::PI/180)/2)) * scale_factor
19
+ y = Math.log(Math.tan((90 + lat) * Math::PI / 360)) / (Math::PI / 180)
20
+ y * scale / 180
21
+ end
22
+
23
+ def self.spherical_mercator_y_to_lat(y,scale=10000)
24
+ #180/Math::PI * (2 * Math.atan(Math.exp(y/scale_factor*Math::PI/180)) - Math::PI/2)
25
+ lat = (y / scale) * 180
26
+ 180/Math::PI * (2 * Math.atan(Math.exp(lat * Math::PI / 180)) - Math::PI / 2)
27
+ end
28
+
29
+ # collects coastline ways into collected_way relations;
30
+ # see http://wiki.openstreetmap.org/wiki/Relations/Proposed/Collected_Ways
31
+ def self.collect_ways(features)
32
+ # collected_ways variable unused review this function
33
+ collected_ways = []
34
+ nodes = {}
35
+ features['osm']['node'].each do |node|
36
+ nodes[node['id']] = node
37
+ end
38
+ features['osm']['way'].each do |way|
39
+ if way['tag']
40
+ coastline = false
41
+ way['tag'].each do |tag|
42
+ if tag['k'] == 'natural' && tag['v'] == 'coastline'
43
+ coastline = true
44
+ break
45
+ end
46
+ end
47
+ if coastline
48
+ relation = {}
49
+ relation['way'] = []
50
+ relation['way'] << way
51
+ # are a way's nodes ordered? yes.
52
+ # check each way to see if it has a first or last node in common with 'way'
53
+ features['osm']['way'].each do |subway|
54
+ if subway['nd'].first['ref'] == nodes[way['nd'].first['ref']] || subway['nd'].first['ref'] == nodes[way['nd'].last['ref']] || subway['nd'].last['ref'] == nodes[way['nd'].first['ref']] || subway['nd'].last['ref'] == nodes[way['nd'].last['ref']]
55
+ # we have a match!
56
+
57
+
58
+
59
+ break
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,2 @@
1
+ require 'cartagen'
2
+ require 'mapknitterExporter'
@@ -0,0 +1,378 @@
1
+ require 'mapknitter-exporter'
2
+ require 'cartagen'
3
+ require 'open3'
4
+
5
+ class MapKnitterExporter
6
+
7
+ def self.ulimit
8
+ # use ulimit to restrict to 7200 CPU seconds and 5gb virtual memory, and 5gB file storage:
9
+ #"ulimit -t 7200 && ulimit -v 5000000 && ulimit -f 5000000 && "
10
+ "ulimit -t 14400 && ulimit -v 5000000 && ulimit -f 10000000 && nice -n 19 "
11
+ end
12
+
13
+ def self.get_working_directory(path)
14
+ "public/warps/#{path}-working/"
15
+ end
16
+
17
+ def self.warps_directory(path)
18
+ "public/warps/#{path}/"
19
+ end
20
+
21
+ def self.delete_temp_files(path)
22
+ system('rm -r ' + get_working_directory(path))
23
+ system('rm ' + warps_directory(path) + '*.png')
24
+ end
25
+
26
+ ########################
27
+ ## Run on each image:
28
+
29
+ # pixels per meter = pxperm
30
+ def self.generate_perspectival_distort(pxperm, id, nodes_array, image_file_name, img_url, height, width, root = "https://mapknitter.org")
31
+ require 'net/http'
32
+
33
+ # everything in -working/ can be deleted;
34
+ # this is just so we can use the files locally outside of s3
35
+ working_directory = get_working_directory(id)
36
+ Dir.mkdir(working_directory) unless (File.exists?(working_directory) && File.directory?(working_directory))
37
+ local_location = "#{working_directory}#{id}-#{image_file_name}"
38
+
39
+ directory = warps_directory(id)
40
+ Dir.mkdir(directory) unless (File.exists?(directory) && File.directory?(directory))
41
+ completed_local_location = directory+id.to_s+'.png'
42
+
43
+ # everything -masked.png can be deleted
44
+ masked_local_location = directory+id.to_s+'-masked.png'
45
+ # everything -mask.png can be deleted
46
+ mask_location = directory+id.to_s+'-mask.png'
47
+ #completed_local_location = directory+id.to_s+'.tif'
48
+ # know everything -unwarped can be deleted
49
+ geotiff_location = directory+id.to_s+'-geo-unwarped.tif'
50
+ # everything -geo WITH AN ID could be deleted, but there is a feature request to preserve these
51
+ warped_geotiff_location = directory+id.to_s+'-geo.tif'
52
+
53
+ northmost = nodes_array.first[:lat]
54
+ southmost = nodes_array.first[:lat]
55
+ westmost = nodes_array.first[:lon]
56
+ eastmost = nodes_array.first[:lon]
57
+
58
+ nodes_array.each do |node|
59
+ northmost = node[:lat] if node[:lat] > northmost
60
+ southmost = node[:lat] if node[:lat] < southmost
61
+ westmost = node[:lon] if node[:lon] < westmost
62
+ eastmost = node[:lon] if node[:lon] > eastmost
63
+ end
64
+
65
+ # puts northmost.to_s+','+southmost.to_s+','+westmost.to_s+','+eastmost.to_s
66
+
67
+ scale = 20037508.34
68
+ y1 = pxperm*Cartagen.spherical_mercator_lat_to_y(northmost,scale)
69
+ x1 = pxperm*Cartagen.spherical_mercator_lon_to_x(westmost,scale)
70
+ y2 = pxperm*Cartagen.spherical_mercator_lat_to_y(southmost,scale)
71
+ x2 = pxperm*Cartagen.spherical_mercator_lon_to_x(eastmost,scale)
72
+ # puts x1.to_s+','+y1.to_s+','+x2.to_s+','+y2.to_s
73
+
74
+ # should determine if it's stored in s3 or locally:
75
+ if (img_url.slice(0,4) == 'http')
76
+ Net::HTTP.start('s3.amazonaws.com') { |http|
77
+ #Net::HTTP.start('localhost') { |http|
78
+ puts (img_url)
79
+ resp = http.get(img_url)
80
+ open(local_location, "wb") { |file|
81
+ file.write(resp.body)
82
+ }
83
+ }
84
+ else
85
+ require "fileutils"
86
+ FileUtils.cp(img_url, local_location)
87
+ end
88
+
89
+ points = ""
90
+ maskpoints = ""
91
+ coordinates = ""
92
+ first = true
93
+
94
+ #EXIF orientation values:
95
+ #Value 0th Row 0th Column
96
+ #1 top left side
97
+ #2 top right side
98
+ #3 bottom right side
99
+ #4 bottom left side
100
+ #5 left side top
101
+ #6 right side top
102
+ #7 right side bottom
103
+ #8 left side bottom
104
+
105
+ rotation = (`identify -format %[exif:Orientation] #{local_location}`).to_i
106
+ #stdin, stdout, stderr = Open3.popen3('identify -format %[exif:Orientation] #{local_location}')
107
+ #rotation = stdout.readlines.first.to_s.to_i
108
+ #puts stderr.readlines
109
+
110
+ if rotation == 6
111
+ puts 'rotated CCW'
112
+ source_corners = source_corners = [[0,height],[0,0],[width,0],[width,height]]
113
+ elsif rotation == 8
114
+ puts 'rotated CW'
115
+ source_corners = [[width,0],[width,height],[0,height],[0,0]]
116
+ elsif rotation == 3
117
+ puts 'rotated 180 deg'
118
+ source_corners = [[width,height],[0,height],[0,0],[width,0]]
119
+ else
120
+ source_corners = [[0,0],[width,0],[width,height],[0,height]]
121
+ end
122
+
123
+ maxdimension = 0
124
+
125
+ nodes_array.each do |node|
126
+ corner = source_corners.shift
127
+ nx1 = corner[0]
128
+ ny1 = corner[1]
129
+ nx2 = -x1+(pxperm*Cartagen.spherical_mercator_lon_to_x(node[:lon],scale))
130
+ ny2 = y1-(pxperm*Cartagen.spherical_mercator_lat_to_y(node[:lat],scale))
131
+
132
+ points = points + ' ' unless first
133
+ maskpoints = maskpoints + ' ' unless first
134
+ points = points + nx1.to_s + ',' + ny1.to_s + ' ' + nx2.to_i.to_s + ',' + ny2.to_i.to_s
135
+ maskpoints = maskpoints + nx2.to_i.to_s + ',' + ny2.to_i.to_s
136
+ first = false
137
+ # we need to find an origin; find northwestern-most point
138
+ coordinates = coordinates+' -gcp '+nx2.to_s+', '+ny2.to_s+', '+node[:lon].to_s + ', ' + node[:lat].to_s
139
+
140
+ # identify largest dimension to set canvas size for ImageMagick:
141
+ maxdimension = nx1.to_i if maxdimension < nx1.to_i
142
+ maxdimension = ny1.to_i if maxdimension < ny1.to_i
143
+ maxdimension = nx2.to_i if maxdimension < nx2.to_i
144
+ maxdimension = ny2.to_i if maxdimension < ny2.to_i
145
+ end
146
+
147
+ # close mask polygon:
148
+ maskpoints = maskpoints + ' '
149
+ nx2 = -x1+(pxperm*Cartagen.spherical_mercator_lon_to_x(nodes_array.first[:lon], scale))
150
+ ny2 = y1-(pxperm*Cartagen.spherical_mercator_lat_to_y(nodes_array.first[:lat], scale))
151
+ maskpoints = maskpoints + nx2.to_i.to_s + ',' + ny2.to_i.to_s
152
+
153
+ height = (y1-y2).to_i.to_s
154
+ width = (-x1+x2).to_i.to_s
155
+
156
+ # http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=11319
157
+ # http://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=8764
158
+ # read about equalization
159
+ # -equalize
160
+ # -contrast-stretch 0
161
+
162
+ imageMagick = "convert "
163
+ imageMagick += "-contrast-stretch 0 "
164
+ imageMagick += local_location+" "
165
+ imageMagick += "-crop "+maxdimension.to_i.to_s+"x"+maxdimension.to_i.to_s+"+0+0! "
166
+ imageMagick += "-flatten "
167
+ imageMagick += "-distort Perspective '"+points+"' "
168
+ imageMagick += "-flatten "
169
+ if width > height
170
+ imageMagick += "-crop "+width+"x"+width+"+0+0\! "
171
+ else
172
+ imageMagick += "-crop "+height+"x"+height+"+0+0\! "
173
+ end
174
+ imageMagick += "+repage "
175
+ imageMagick += completed_local_location
176
+ puts imageMagick
177
+ system(self.ulimit+imageMagick)
178
+
179
+ # create a mask (later we can blur edges here)
180
+ imageMagick2 = 'convert +antialias '
181
+ if width > height
182
+ imageMagick2 += "-size "+width+"x"+width+" "
183
+ else
184
+ imageMagick2 += "-size "+height+"x"+height+" "
185
+ end
186
+ # attempt at blurred edges in masking, but I've given up, as gdal_merge doesn't seem to respect variable-opacity alpha channels
187
+ imageMagick2 += ' xc:none -draw "fill black stroke red stroke-width 30 polyline '
188
+ imageMagick2 += maskpoints + '" '
189
+ imageMagick2 += ' -alpha set -channel A -transparent red -blur 0x8 -channel R -evaluate set 0 +channel '+mask_location
190
+ #imageMagick2 += ' xc:none -draw "fill black stroke none polyline '
191
+ #imageMagick2 += maskpoints + '" '
192
+ #imageMagick2 += ' '+mask_location
193
+ puts imageMagick2
194
+ system(self.ulimit+imageMagick2)
195
+
196
+ imageMagick3 = 'composite '+mask_location+' '+completed_local_location+' -compose DstIn -alpha Set '+masked_local_location
197
+ puts imageMagick3
198
+ system(self.ulimit+imageMagick3)
199
+
200
+ gdal_translate = "gdal_translate -of GTiff -a_srs EPSG:4326 "+coordinates+' -co "TILED=NO" '+masked_local_location+' '+geotiff_location
201
+ puts gdal_translate
202
+ system(self.ulimit+gdal_translate)
203
+
204
+ #gdalwarp = 'gdalwarp -srcnodata "255" -dstnodata 0 -cblend 30 -of GTiff -t_srs EPSG:4326 '+geotiff_location+' '+warped_geotiff_location
205
+ gdalwarp = 'gdalwarp -of GTiff -t_srs EPSG:4326 '+geotiff_location+' '+warped_geotiff_location
206
+ puts gdalwarp
207
+ system(self.ulimit+gdalwarp)
208
+
209
+ # deletions could happen here; do it in distinct method so we can run it independently
210
+ delete_temp_files(id)
211
+
212
+ [x1,y1]
213
+ end
214
+
215
+ ########################
216
+ ## Run on maps:
217
+
218
+ # distort all warpables, returns upper left corner coords in x,y
219
+ def self.distort_warpables(scale, images, export, id)
220
+
221
+ puts '> generating geotiffs of each warpable in GDAL'
222
+ lowest_x = 0
223
+ lowest_y = 0
224
+ all_coords = []
225
+ current = 0
226
+ images.each do |image|
227
+ current += 1
228
+
229
+ ## TODO: refactor to generate static status file:
230
+ export.status = 'warping '+current.to_s+' of '+images.length.to_s
231
+ puts 'warping '+current.to_s+' of '+images.length.to_s
232
+ export.save
233
+ ##
234
+
235
+ img_coords = generate_perspectival_distort(
236
+ scale,
237
+ id,
238
+ image[:nodes_array],
239
+ image[:filename],
240
+ image[:url],
241
+ image[:height],
242
+ image[:width]
243
+ )
244
+ puts '- '+img_coords.to_s
245
+ all_coords << img_coords
246
+ lowest_x = img_coords.first if (img_coords.first < lowest_x || lowest_x == 0)
247
+ lowest_y = img_coords.last if (img_coords.last < lowest_y || lowest_y == 0)
248
+ end
249
+ [lowest_x, lowest_y, all_coords]
250
+ end
251
+
252
+ # generate a tiff from all warpable images in this set
253
+ def self.generate_composite_tiff(coords, origin, placed_warpables, id, ordered)
254
+ directory = "public/warps/#{id}/"
255
+ composite_location = directory + id.to_s + '-geo.tif'
256
+ geotiffs = ''
257
+ minlat = nil
258
+ minlon = nil
259
+ maxlat = nil
260
+ maxlon = nil
261
+ placed_warpables.each do |warpable|
262
+ warpable[:nodes_array].each do |n|
263
+ minlat = n[:lat] if minlat == nil || n[:lat] < minlat
264
+ minlon = n[:lon] if minlon == nil || n[:lon] < minlon
265
+ maxlat = n[:lat] if maxlat == nil || n[:lat] > maxlat
266
+ maxlon = n[:lon] if maxlon == nil || n[:lon] > maxlon
267
+ end
268
+ end
269
+ first = true
270
+ if ordered != true
271
+ # sort by area; this would be overridden by a provided order
272
+ warpables = placed_warpables.sort{|a,b|b.poly_area <=> a.poly_area}
273
+ end
274
+ warpables.each do |warpable|
275
+ wid = warpable[:id].to_s
276
+ geotiffs += ' '+directory+wid+'-geo.tif'
277
+ if first
278
+ gdalwarp = "gdalwarp -s_srs EPSG:3857 -te #{minlon} #{minlat} #{maxlon} #{maxlat} #{directory}#{wid}-geo.tif #{directory}#{id}-geo.tif"
279
+ first = false
280
+ else
281
+ gdalwarp = "gdalwarp #{directory}#{wid}-geo.tif #{directory}#{id}-geo.tif"
282
+ end
283
+ puts gdalwarp
284
+ system(self.ulimit+gdalwarp)
285
+ end
286
+ composite_location
287
+ end
288
+
289
+ # generates a tileset at root/public/tms/<id>/
290
+ # root is something like https://mapknitter.org
291
+ def self.generate_tiles(key, id, root)
292
+ key = "AIzaSyAOLUQngEmJv0_zcG1xkGq-CXIPpLQY8iQ" if key == "" # ugh, let's clean this up!
293
+ key = key || "AIzaSyAOLUQngEmJv0_zcG1xkGq-CXIPpLQY8iQ"
294
+ gdal2tiles = "gdal2tiles.py -k --s_srs EPSG:3857 -t #{id} -g #{key} public/warps/#{id}/#{id}-geo.tif public/tms/#{id}/"
295
+ puts gdal2tiles
296
+ system(self.ulimit+gdal2tiles)
297
+ end
298
+
299
+ # zips up tiles at public/tms/<id>.zip;
300
+ def self.zip_tiles(id)
301
+ rmzip = "cd public/tms/ && rm #{id}.zip && cd ../../"
302
+ system(rmzip)
303
+ zip = "cd public/tms/ && #{self.ulimit} zip -rq #{id}.zip #{id}/ && cd ../../"
304
+ system(zip)
305
+ end
306
+
307
+ # generates a tileset at public/tms/<id>/
308
+ def self.generate_jpg(id, root)
309
+ imageMagick = "convert -background white -flatten public/warps/#{id}/#{id}-geo.tif #{root}/public/warps/#{id}/#{id}.jpg"
310
+ system(self.ulimit+imageMagick)
311
+ end
312
+
313
+ # runs the above map functions while maintaining a record of state in an Export model;
314
+ # we'll be replacing the export model state with a flat status file
315
+ def self.run_export(user_id, resolution, export, id, root, placed_warpables, key)
316
+ export.user_id = user_id if user_id
317
+ export.status = 'starting'
318
+ export.tms = false
319
+ export.geotiff = false
320
+ export.zip = false
321
+ export.jpg = false
322
+ export.save
323
+
324
+ directory = "#{root}/public/warps/#{id}/"
325
+ stdin, stdout, stderr = Open3.popen3('rm -r '+directory.to_s)
326
+ puts stdout.readlines
327
+ puts stderr.readlines
328
+ stdin, stdout, stderr = Open3.popen3("rm -r #{root}/public/tms/#{id}")
329
+ puts stdout.readlines
330
+ puts stderr.readlines
331
+
332
+ puts '> averaging scales; resolution: ' + resolution.to_s
333
+ pxperm = 100/(resolution).to_f # pixels per meter
334
+ puts '> scale: ' + pxperm.to_s + 'pxperm'
335
+
336
+ puts '> distorting warpables'
337
+
338
+ origin = self.distort_warpables(pxperm, placed_warpables, export, id)
339
+ warpable_coords = origin.pop
340
+
341
+ export.status = 'compositing'
342
+ export.save
343
+
344
+ puts '> generating composite tiff'
345
+ composite_location = self.generate_composite_tiff(warpable_coords,origin,placed_warpables,id,false) # no ordering yet
346
+
347
+ info = (`identify -quiet -format '%b,%w,%h' #{composite_location}`).split(',')
348
+ puts info
349
+
350
+ if info[0] != ''
351
+ export.geotiff = true
352
+ export.size = info[0]
353
+ export.width = info[1]
354
+ export.height = info[2]
355
+ export.cm_per_pixel = 100.0000/pxperm
356
+ export.status = 'tiling'
357
+ export.save
358
+ end
359
+
360
+ puts '> generating tiles'
361
+ export.tms = true if self.generate_tiles(key, id, root)
362
+ export.status = 'zipping tiles'
363
+ export.save
364
+
365
+ puts '> zipping tiles'
366
+ export.zip = true if self.zip_tiles(id)
367
+ export.status = 'creating jpg'
368
+ export.save
369
+
370
+ puts '> generating jpg'
371
+ export.jpg = true if self.generate_jpg(id, root)
372
+ export.status = 'complete'
373
+ export.save
374
+
375
+ export.status
376
+ end
377
+
378
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mapknitter-exporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jeffrey Warren
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: The GDAL/ImageMagick-based exporter system from MapKnitter
14
+ email: jeff@unterbahn.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/cartagen.rb
20
+ - lib/mapknitter-exporter.rb
21
+ - lib/mapknitterExporter.rb
22
+ homepage: http://rubygems.org/gems/mapknitter-exporter
23
+ licenses:
24
+ - GPLv3
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.6.14.4
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: The GDAL/ImageMagick-based exporter system from MapKnitter
46
+ test_files: []