aqila-mapas 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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