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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1223 -0
- data/.travis.yml +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +49 -0
- data/README.md +31 -0
- data/Rakefile +8 -0
- data/aqila-mapas.gemspec +36 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/aqila/mapas/railtie.rb +13 -0
- data/lib/aqila/mapas/version.rb +7 -0
- data/lib/aqila/mapas.rb +57 -0
- data/lib/map/download_tiles_service.rb +74 -0
- data/lib/map/file_import_basic.rb +30 -0
- data/lib/map/float.rb +13 -0
- data/lib/map/gdal/base.rb +66 -0
- data/lib/map/gdal/colorize_service.rb +82 -0
- data/lib/map/gdal/contrast_stretch_service.rb +26 -0
- data/lib/map/gdal/crop_service.rb +36 -0
- data/lib/map/gdal/gdal_info_service.rb +32 -0
- data/lib/map/gdal/grid_service.rb +36 -0
- data/lib/map/gdal/merge_service.rb +22 -0
- data/lib/map/gdal/ndvi_service.rb +32 -0
- data/lib/map/gdal/ogr2ogr_service.rb +23 -0
- data/lib/map/gdal/ogri_info_service.rb +35 -0
- data/lib/map/gdal/polygonize_service.rb +36 -0
- data/lib/map/gdal/raster_service.rb +36 -0
- data/lib/map/gdal/rgb_service.rb +27 -0
- data/lib/map/gdal/table_colors.txt +52 -0
- data/lib/map/gdal/tiles_service.rb +21 -0
- data/lib/map/gdal/translate_service.rb +37 -0
- data/lib/map/gdal/warp_service.rb +22 -0
- data/lib/map/gleba_tiles_service.rb +29 -0
- data/lib/map/gpx_service.rb +34 -0
- data/lib/map/kml_creator_line_service.rb +31 -0
- data/lib/map/kml_creator_service.rb +31 -0
- data/lib/map/kml_edit_service.rb +122 -0
- data/lib/map/kml_offset_service.rb +59 -0
- data/lib/map/kml_service.rb +35 -0
- data/lib/map/lat_lon_service.rb +93 -0
- data/lib/map/polygon_service.rb +100 -0
- data/lib/map/rgeo_service.rb +42 -0
- data/lib/map/shape_to_tif_service.rb +89 -0
- data/lib/map/tile_service.rb +22 -0
- data/lib/map/tiles_base.rb +11 -0
- data/lib/map/tracking_cleaner_service.rb +78 -0
- data/lib/satellite/imagery_proccessor.rb +70 -0
- data/lib/satellite/landsat8/coordinate_converter_service.rb +40 -0
- data/lib/satellite/landsat8/imagery_service.rb +113 -0
- data/lib/satellite/sentinel2/coordinate_converter_service.rb +90 -0
- data/lib/satellite/sentinel2/imagery_service.rb +144 -0
- 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
|