aqila-mapas 0.4.4

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +1223 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +14 -0
  7. data/Gemfile.lock +49 -0
  8. data/README.md +31 -0
  9. data/Rakefile +8 -0
  10. data/aqila-mapas.gemspec +36 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/lib/aqila/mapas/railtie.rb +13 -0
  14. data/lib/aqila/mapas/version.rb +7 -0
  15. data/lib/aqila/mapas.rb +57 -0
  16. data/lib/map/download_tiles_service.rb +74 -0
  17. data/lib/map/file_import_basic.rb +30 -0
  18. data/lib/map/float.rb +13 -0
  19. data/lib/map/gdal/base.rb +66 -0
  20. data/lib/map/gdal/colorize_service.rb +82 -0
  21. data/lib/map/gdal/contrast_stretch_service.rb +26 -0
  22. data/lib/map/gdal/crop_service.rb +36 -0
  23. data/lib/map/gdal/gdal_info_service.rb +32 -0
  24. data/lib/map/gdal/grid_service.rb +36 -0
  25. data/lib/map/gdal/merge_service.rb +22 -0
  26. data/lib/map/gdal/ndvi_service.rb +32 -0
  27. data/lib/map/gdal/ogr2ogr_service.rb +23 -0
  28. data/lib/map/gdal/ogri_info_service.rb +35 -0
  29. data/lib/map/gdal/polygonize_service.rb +36 -0
  30. data/lib/map/gdal/raster_service.rb +36 -0
  31. data/lib/map/gdal/rgb_service.rb +27 -0
  32. data/lib/map/gdal/table_colors.txt +52 -0
  33. data/lib/map/gdal/tiles_service.rb +21 -0
  34. data/lib/map/gdal/translate_service.rb +37 -0
  35. data/lib/map/gdal/warp_service.rb +22 -0
  36. data/lib/map/gleba_tiles_service.rb +29 -0
  37. data/lib/map/gpx_service.rb +34 -0
  38. data/lib/map/kml_creator_line_service.rb +31 -0
  39. data/lib/map/kml_creator_service.rb +31 -0
  40. data/lib/map/kml_edit_service.rb +122 -0
  41. data/lib/map/kml_offset_service.rb +59 -0
  42. data/lib/map/kml_service.rb +35 -0
  43. data/lib/map/lat_lon_service.rb +93 -0
  44. data/lib/map/polygon_service.rb +100 -0
  45. data/lib/map/rgeo_service.rb +42 -0
  46. data/lib/map/shape_to_tif_service.rb +89 -0
  47. data/lib/map/tile_service.rb +22 -0
  48. data/lib/map/tiles_base.rb +11 -0
  49. data/lib/map/tracking_cleaner_service.rb +78 -0
  50. data/lib/satellite/imagery_proccessor.rb +70 -0
  51. data/lib/satellite/landsat8/coordinate_converter_service.rb +40 -0
  52. data/lib/satellite/landsat8/imagery_service.rb +113 -0
  53. data/lib/satellite/sentinel2/coordinate_converter_service.rb +90 -0
  54. data/lib/satellite/sentinel2/imagery_service.rb +144 -0
  55. metadata +137 -0
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::Gdal::NdviService
4
+ include Map::Gdal::Base
5
+
6
+ def initialize(tif_red, tif_near_red)
7
+ @tif_red = tif_red
8
+ @tif_near_red = tif_near_red
9
+ end
10
+
11
+ def call
12
+ tif_out = get_path_to_temp_file('calculated', 'tif')
13
+ add_to_clean(tif_out)
14
+ add_to_clean("#{tif_out}.aux.xml")
15
+
16
+ run_command(
17
+ %{
18
+ gdal_calc.py
19
+ --overwrite
20
+ -B #{@tif_red}
21
+ -A #{@tif_near_red}
22
+ --outfile=#{tif_out}
23
+ --calc="(A.astype(float)-B)/(A.astype(float)+B)"
24
+ --overwrite
25
+ --type=Float32
26
+ --NoDataValue=-9999
27
+ }.squish
28
+ )
29
+
30
+ tif_out
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ # ogr2ogr - Converts simple features data between file formats.
3
+ # http://www.gdal.org/ogr2ogr.html
4
+ class Map::Gdal::Ogr2ogrService
5
+ include Map::Gdal::Base
6
+
7
+ attr_reader :file
8
+ def initialize(file)
9
+ raise 'File does not exist' unless File.exist?(file)
10
+ @file = file
11
+ end
12
+
13
+ def call(options)
14
+ raise 'File does no exist' unless File.exist?(file)
15
+ raise 'Format does not specified' unless options[:f]
16
+
17
+ out = get_path_to_temp_file(:ogr2ogr, options[:f])
18
+ add_to_clean(out)
19
+
20
+ run_command(%{ogr2ogr #{options_to_command_line(options)} "#{out}" "#{file}"})
21
+ out
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::Gdal::OgriInfoService
4
+ include Map::Gdal::Base
5
+
6
+ attr_reader :file
7
+ def initialize(file)
8
+ raise 'File does not exist' unless File.exist?(file)
9
+ @file = file
10
+ end
11
+
12
+ def layer_name
13
+ # quando é um shapefile a informação vem seguida do tipo de dado
14
+ # Exemplo: Campinho (Point)
15
+ call(q: '').gsub(/\d+:/, '').gsub(/\s\(.+$/, '').strip
16
+ end
17
+
18
+ def limits
19
+ # Saída de exemplo Extent: (-52.726602, -27.567020) - (-52.714728, -27.554208)
20
+ # O match retorna[1] (-52.726602, -27.567020) - (-52.714728, -27.554208)
21
+ # O primeiro gsub retorna (-52.726602, -27.567020) (-52.714728, -27.554208)
22
+ # O segundo gsub retorna -52.726602 -27.567020 -52.714728 -27.554208
23
+ call({}, layer_name)
24
+ .match(/Extent: (.+)/)[1]
25
+ .gsub(/\s-\s/, ' ')
26
+ .gsub(/[^0-9\.\s-]/, '')
27
+ end
28
+
29
+ private
30
+
31
+ def call(options = {}, layer = nil)
32
+ layer = "\"#{layer}\"" if layer
33
+ run_command(%{ogrinfo -geom=NO -ro #{options_to_command_line(options)} "#{file}" #{layer}})
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zip'
4
+
5
+ class Map::Gdal::PolygonizeService
6
+ include Map::Gdal::Base
7
+
8
+ def initialize(tif_path)
9
+ @tif_path = tif_path
10
+ end
11
+
12
+ def call(options = {})
13
+ if options[:kml]
14
+ @tif_path = Map::Gdal::CropService.new(@tif_path).call(reference: IO.binread(options[:kml]))
15
+ add_to_clean(@tif_path)
16
+ end
17
+
18
+ file_id = "#{Time.current.to_i}-#{(rand * 1_000).to_i}"
19
+ destination_shp = get_path_to_temp_file('shapefile', 'shp', file_id)
20
+ zip_file = get_path_to_temp_file('shapefile', 'zip', file_id)
21
+
22
+ run_command(%{gdal_polygonize.py #{@tif_path} -f "ESRI Shapefile" #{destination_shp}})
23
+
24
+ Zip::File.open(zip_file, Zip::File::CREATE) do |zip|
25
+ %w(dbf prj shp shx).each do |extension|
26
+ file_name = "shapefile-#{file_id}.#{extension}"
27
+ file_with_path = get_path_to_temp_file('shapefile', extension, file_id)
28
+ zip.add(file_name, file_with_path)
29
+ add_to_clean(file_with_path)
30
+ end
31
+ end
32
+
33
+ add_to_clean(zip_file)
34
+ zip_file
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::Gdal::RasterService
4
+ include Map::Gdal::Base
5
+
6
+ def initialize(file_input)
7
+ raise 'File does not exist' unless File.exist?(file_input)
8
+
9
+ @file = file_input
10
+ end
11
+
12
+ def call(options)
13
+ out = get_path_to_temp_file(:raster, options[:format])
14
+ add_to_clean(out)
15
+ add_to_clean("#{out}.aux.xml")
16
+
17
+ format_out = if options[:format] == 'tif'
18
+ 'GTiff'
19
+ else
20
+ options[:format].upcase
21
+ end
22
+
23
+ begin
24
+ run_command(%{gdal_rasterize -q -of #{format_out} #{options_to_command_line(options, :format)} -l "#{get_layer_name}" "#{@file}" "#{out}"})
25
+ rescue RuntimeError => re
26
+ if re.message.include?('Failed to find field')
27
+ cpg_iso = "#{get_file_name_with_path(@file)}.cpg"
28
+ run_command(%{echo 885910 > "#{cpg_iso}"})
29
+ retry
30
+ elsif
31
+ raise re
32
+ end
33
+ end
34
+ out
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::Gdal::RgbService
4
+ include Map::Gdal::Base
5
+
6
+ attr_reader :merge
7
+
8
+ delegate :clean, to: :merge
9
+
10
+ def initialize(files)
11
+ @merge = Map::Gdal::MergeService.new(files)
12
+ end
13
+
14
+ def call
15
+ # http://www.processamentodigital.com.br/2013/11/23/qgis-2-0-composicao-colorida-rgb-para-imagens-landsat-8/
16
+
17
+ out = @merge.call([
18
+ '-separate',
19
+ '-co TFW=yes',
20
+ '-of GTiff'
21
+ ])
22
+
23
+ add_to_clean(out)
24
+
25
+ out
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # Exportar arquivo de mapa de cores gerado pela QGIS
2
+ #INTERPOLATION:INTERPOLATED
3
+ 1,26,150,65,255,1.000000
4
+ 1.00142,37,155,68,255,1.001421
5
+ 1.00284,48,160,71,255,1.002843
6
+ 1.00426,60,166,75,255,1.004264
7
+ 1.00569,71,171,78,255,1.005686
8
+ 1.00711,83,177,81,255,1.007107
9
+ 1.00853,94,182,85,255,1.008529
10
+ 1.00995,106,188,88,255,1.009950
11
+ 1.01137,117,193,91,255,1.011371
12
+ 1.01279,128,199,95,255,1.012793
13
+ 1.01421,140,204,98,255,1.014214
14
+ 1.01564,151,210,101,255,1.015636
15
+ 1.01706,163,215,105,255,1.017057
16
+ 1.01848,171,219,111,255,1.018479
17
+ 1.0199,178,222,118,255,1.019900
18
+ 1.02132,185,225,125,255,1.021321
19
+ 1.02274,193,228,132,255,1.022743
20
+ 1.02416,200,231,139,255,1.024164
21
+ 1.02559,207,234,146,255,1.025586
22
+ 1.02701,215,237,153,255,1.027007
23
+ 1.02843,222,241,160,255,1.028429
24
+ 1.02985,229,244,167,255,1.029850
25
+ 1.03127,236,247,174,255,1.031271
26
+ 1.03269,244,250,181,255,1.032693
27
+ 1.03411,251,253,188,255,1.034114
28
+ 1.03554,254,251,188,255,1.035536
29
+ 1.03696,254,245,180,255,1.036957
30
+ 1.03838,254,238,172,255,1.038379
31
+ 1.0398,254,231,164,255,1.039800
32
+ 1.04122,254,225,157,255,1.041221
33
+ 1.04264,254,218,149,255,1.042643
34
+ 1.04406,253,212,141,255,1.044064
35
+ 1.04549,253,205,133,255,1.045486
36
+ 1.04691,253,198,126,255,1.046907
37
+ 1.04833,253,192,118,255,1.048329
38
+ 1.04975,253,185,110,255,1.049750
39
+ 1.05117,253,178,102,255,1.051171
40
+ 1.05259,252,170,95,255,1.052593
41
+ 1.05401,249,158,89,255,1.054014
42
+ 1.05544,246,146,84,255,1.055436
43
+ 1.05686,242,134,78,255,1.056857
44
+ 1.05828,239,122,73,255,1.058279
45
+ 1.0597,236,110,67,255,1.059700
46
+ 1.06112,233,97,61,255,1.061121
47
+ 1.06254,230,85,56,255,1.062543
48
+ 1.06396,227,73,50,255,1.063964
49
+ 1.06539,224,61,44,255,1.065386
50
+ 1.06681,221,49,39,255,1.066807
51
+ 1.06823,218,37,33,255,1.068229
52
+ 1.06965,215,25,28,255,1.069650
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::Gdal::TilesService
4
+ include Map::Gdal::Base
5
+
6
+ DEFAULT_ZOOM_MAX = 15
7
+
8
+ def initialize(file)
9
+ @file = file
10
+ end
11
+
12
+ def call(options = {})
13
+ tiles_folder = options[:output] || File.join(Dir.tmpdir, "tiles-#{(Time.current.to_i * rand).to_i}")
14
+
15
+ run_command("gdal2tiles.py --profile=mercator -z #{options[:zoom_min].to_i}-#{options[:zoom_max] || DEFAULT_ZOOM_MAX} #{@file} #{tiles_folder}")
16
+
17
+ add_to_clean(tiles_folder)
18
+
19
+ tiles_folder
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::Gdal::TranslateService
4
+ include Map::Gdal::Base
5
+
6
+ def initialize(file)
7
+ @file = file
8
+ end
9
+
10
+ def to_png(options = {})
11
+ self.call(options.merge({
12
+ of: 'PNG',
13
+ co: 'worldfile=yes',
14
+ scale: '',
15
+ ot: 'Byte'
16
+ }))
17
+ end
18
+
19
+ def to_jpeg(options = {})
20
+ self.call(options.merge({
21
+ of: 'JPEG',
22
+ co: 'worldfile=yes'
23
+ }))
24
+ end
25
+
26
+ def call(options = {})
27
+ out = get_path_to_temp_file(:translate, options[:of])
28
+
29
+ run_command("gdal_translate #{options_to_command_line(options)} #{@file} #{out}")
30
+
31
+ add_to_clean(out)
32
+ add_to_clean("#{out}.aux.xml")
33
+ add_to_clean(out.gsub('.jpg', '.wld'))
34
+
35
+ out
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # gdalwarp - image reprojection and warping utility
4
+ # http://www.gdal.org/gdalwarp.html
5
+ class Map::Gdal::WarpService
6
+ include Map::Gdal::Base
7
+
8
+ attr_reader :tif
9
+ def initialize(tif)
10
+ @tif = tif
11
+ end
12
+
13
+ def call(options)
14
+ raise "Arquivo TIF inválido. [#{tif.inspect}]" unless tif && File.exist?(tif)
15
+
16
+ out = get_path_to_temp_file(:warp, :tif)
17
+ run_command(%{gdalwarp -q -overwrite #{options_to_command_line(options)} "#{tif}" "#{out}"})
18
+
19
+ add_to_clean(out)
20
+ out
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::GlebaTilesService
4
+ ZOOM_MIN = 1
5
+ ZOOM_MAX = 15
6
+ OUTPUT_DIR = 'public/map_tiles'
7
+
8
+ def initialize(gleba)
9
+ @gleba = gleba
10
+ end
11
+
12
+ def download_all_tiles
13
+ download_service.download
14
+ end
15
+
16
+ def download_service
17
+ @download_service ||= Map::DownloadTilesService.new({
18
+ boundary_ne: kml_service.point_more_north_east,
19
+ boundary_sw: kml_service.point_more_south_west,
20
+ zoom_min: ZOOM_MIN,
21
+ zoom_max: ZOOM_MAX,
22
+ output_directory: OUTPUT_DIR
23
+ })
24
+ end
25
+
26
+ def kml_service
27
+ @kml_service ||= Map::KmlService.new(@gleba.arquivo_kml)
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::GpxService
4
+ include Map::FileImportBasic
5
+
6
+ def initialize(gpx)
7
+ @gpx = gpx
8
+ end
9
+
10
+ def coordinates
11
+ unless @coordinates
12
+ xml_doc = Nokogiri::XML(@gpx)
13
+ coordinates = xml_doc.xpath("//*[local-name()='trkseg']")
14
+ raise 'Arquivo inválido' if coordinates.empty?
15
+ @coordinates = coordinates.to_a.collect do |coordinate|
16
+ coordinate.children.to_a.collect do |point|
17
+ to_coordinate(point)
18
+ end.compact
19
+ end
20
+ end
21
+
22
+ @coordinates
23
+ end
24
+
25
+ private
26
+
27
+ def to_coordinate(point)
28
+ [
29
+ point.attributes['lon'].value.to_f,
30
+ point.attributes['lat'].value.to_f
31
+ ]
32
+ rescue
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::KmlCreatorLineService
4
+ def initialize
5
+ @lines = []
6
+ end
7
+
8
+ def add_line(coordinates)
9
+ @lines << coordinates
10
+ self
11
+ end
12
+
13
+ def to_xml
14
+ Nokogiri::XML::Builder.new do |xml|
15
+ xml.kml do
16
+ xml.Document do
17
+ xml.name 'KML_GENERATED'
18
+ @lines.each_with_index do |coordinates, index|
19
+ xml.Placemark do
20
+ xml.name "Line#{index}"
21
+ xml.LineString do
22
+ xml.tessellate 1
23
+ xml.coordinates coordinates.map{ |item| "#{item.second},#{item.first},0" }.join("\n")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end.to_xml
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::KmlCreatorService
4
+ def add_polygon(polygon)
5
+ (@polygons ||= []) << polygon
6
+ self
7
+ end
8
+
9
+ def to_xml
10
+ Nokogiri::XML::Builder.new do |xml|
11
+ xml.kml do
12
+ xml.Document do
13
+ xml.name 'KML_GENERATED'
14
+ @polygons.to_a.each_with_index do |polygon, index|
15
+ xml.Placemark do
16
+ xml.name "Polygon#{index}"
17
+ xml.Polygon do
18
+ xml.outerBoundaryIs do
19
+ xml.LinearRing do
20
+ xml.tessellate 1
21
+ xml.coordinates polygon.map{ |item| "#{item.second},#{item.first},0" }.join("\n")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end.to_xml
30
+ end
31
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::KmlEditService
4
+ def initialize(raw_kml)
5
+ @raw_kml = raw_kml
6
+ adjust_invalid_kml!
7
+ end
8
+
9
+ # add extended data do KML
10
+ # Based on https://developers.google.com/kml/documentation/extendeddata
11
+ def add_definition_custom_field(name, type)
12
+ schema = Nokogiri::XML::Node.new('Schema', nokogiri)
13
+ schema['name'] = name
14
+ schema['id'] = "#{name}Id"
15
+ simple_field = Nokogiri::XML::Node.new('SimpleField', nokogiri)
16
+ simple_field['type'] = type
17
+ simple_field['name'] = name
18
+ schema << simple_field
19
+
20
+ document << schema
21
+
22
+ self
23
+ end
24
+
25
+ # Add new point to KML (Placemark)
26
+ def add_point(lat, lon, data = {})
27
+ index = document.css('Placemark').length + 1
28
+ placemark = Nokogiri::XML::Node.new('Placemark', nokogiri)
29
+ name = Nokogiri::XML::Node.new('name', nokogiri)
30
+ name.content = "Point #{index}"
31
+
32
+ # ExtendedData
33
+ data.each do |key, value|
34
+ add_extended_data(key, value, placemark)
35
+ end
36
+
37
+ # Point
38
+ point = Nokogiri::XML::Node.new('Point', nokogiri)
39
+ coordinates = Nokogiri::XML::Node.new('coordinates', nokogiri)
40
+ coordinates.content = "#{lon},#{lat}"
41
+ point << coordinates
42
+
43
+ placemark << name
44
+ placemark << point
45
+
46
+ document_or_folder << placemark
47
+
48
+ self
49
+ end
50
+
51
+ def add_default_value_to_placemarks(data)
52
+ data.each do |name, value|
53
+ check_definition!(name)
54
+
55
+ document.css('Placemark').each do |placemark|
56
+ add_extended_data(name, value, placemark)
57
+ end
58
+ end
59
+
60
+ self
61
+ end
62
+
63
+ def to_xml
64
+ nokogiri.to_xml
65
+ end
66
+
67
+ def remove_node(name)
68
+ document.css(name.to_s).each(&:remove)
69
+
70
+ self
71
+ end
72
+
73
+ private
74
+
75
+ def nokogiri
76
+ @nokogiri ||= Nokogiri::XML(@raw_kml)
77
+ end
78
+
79
+ def document
80
+ @document ||= nokogiri.at_css('Document')
81
+ end
82
+
83
+ def document_or_folder
84
+ @folder ||= nokogiri.at_css('Folder') || document
85
+ end
86
+
87
+ def check_definition!(name)
88
+ has_definition = document.css("Schema SimpleField[name=#{name}]").any?
89
+ raise "#{name} is not defined" unless has_definition
90
+ end
91
+
92
+ def add_extended_data(name, value, node)
93
+ check_definition!(name)
94
+
95
+ extendedData = Nokogiri::XML::Node.new('ExtendedData', nokogiri)
96
+ schemaData = Nokogiri::XML::Node.new('SchemaData', nokogiri)
97
+ schemaData['schemaUrl'] = "##{name}Id"
98
+ extendedData << schemaData
99
+
100
+ simpleData = Nokogiri::XML::Node.new('SimpleData', nokogiri)
101
+ simpleData['name'] = name
102
+ simpleData.content = value
103
+ schemaData << simpleData
104
+
105
+ node << extendedData
106
+ end
107
+
108
+ def adjust_invalid_kml!
109
+ # Existem alguns KMLs (inválidos) que o nodo root é KML e não existe um document dentro
110
+ # Nestes casos é necessário trocar o KML por Document
111
+ unless nokogiri.at_css('Document')
112
+ @raw_kml = @raw_kml.gsub('<kml', '<Document').gsub('</kml', '</Document')
113
+ @nokogiri = nil
114
+ end
115
+
116
+ # Gera um nome randomico para a layer
117
+ if document_or_folder
118
+ node_name = document_or_folder.at_css('name')
119
+ node_name.content = "layer_name_#{Time.current.to_i}"
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::KmlOffsetService
4
+ MINIMUM_POINTS = 3
5
+
6
+ attr_reader :kml
7
+
8
+ def initialize(kml)
9
+ @kml = kml
10
+ end
11
+
12
+ def call(offset)
13
+ polygons = coordinates.flat_map do |point|
14
+ call_python(point.map(&:reverse), offset) if point.length > MINIMUM_POINTS
15
+ end.compact
16
+
17
+ generate_kml(polygons)
18
+ end
19
+
20
+ private
21
+
22
+ def coordinates
23
+ @coordinates ||= Map::KmlService.new(kml).coordinates
24
+ end
25
+
26
+ def generate_kml(polygons)
27
+ service = Map::KmlCreatorService.new
28
+ polygons.each { |polygon| service.add_polygon(polygon) }
29
+ service.to_xml
30
+ end
31
+
32
+ # Este método gera um programa em python para depois rodar
33
+ # Foi a forma encontrada para poder usar a lib pyclipper
34
+ # https://pypi.python.org/pypi/pyclipper
35
+ def call_python(points, offset)
36
+ str = <<-EOF.strip_heredoc
37
+ import pyclipper
38
+
39
+ subj = (#{points.map { |point| "(#{point.first.to_f}, #{point.second.to_f})" }.join(',')})
40
+
41
+ pco = pyclipper.PyclipperOffset()
42
+ pco.AddPath(pyclipper.scale_to_clipper(subj), pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
43
+
44
+ solution = pco.Execute(#{offset})
45
+
46
+ print(pyclipper.scale_from_clipper(solution))
47
+ EOF
48
+
49
+ file = "/tmp/offset-#{Time.current.to_i}-#{(rand * 1_000).to_i}"
50
+ IO.write(file, str)
51
+ command = "python3 #{file}"
52
+ result = `#{command} 2>&1`
53
+ raise "Falha ao rodar comando [$ #{command}]\n[#{result}]" unless $?.success?
54
+
55
+ FileUtils.rm_rf(file)
56
+
57
+ JSON.parse(result)
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::KmlService
4
+ include Map::FileImportBasic
5
+
6
+ def initialize(kml)
7
+ @kml = kml
8
+ end
9
+
10
+ def coordinates
11
+ unless @coordinates
12
+ xml_doc =
13
+ if @kml.is_a? Nokogiri::XML::Element
14
+ @kml
15
+ else
16
+ Nokogiri::XML(@kml)
17
+ end
18
+ coordinates = xml_doc.xpath(".//*[local-name()='coordinates']")
19
+
20
+ @coordinates = coordinates.to_a.collect do |coordinate|
21
+ coordinate.text.split(' ').collect do |polygon|
22
+ string_to_coordinate(polygon)
23
+ end
24
+ end
25
+ end
26
+
27
+ @coordinates
28
+ end
29
+
30
+ private
31
+
32
+ def string_to_coordinate(str)
33
+ str.split(',')[0..1].map(&:to_f)
34
+ end
35
+ end