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,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ =begin
4
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
5
+ /* Latitude/longitude spherical geodesy tools (c) Chris Veness 2002-2016 */
6
+ /* MIT Licence */
7
+ /* www.movable-type.co.uk/scripts/latlong.html */
8
+ /* www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-spherical.html */
9
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
10
+ Código portado de biblioteca LatLon do JS
11
+ =end
12
+
13
+ class Map::LatLonService
14
+ attr_reader :lat, :lon
15
+
16
+ def initialize(lat_or_coordinate, lon = nil)
17
+ if lon
18
+ @lat = lat_or_coordinate.to_f
19
+ @lon = lon.to_f
20
+ elsif lat_or_coordinate.is_a?(Array)
21
+ @lat = lat_or_coordinate.first.to_f
22
+ @lon = lat_or_coordinate.second.to_f
23
+ elsif lat_or_coordinate.is_a?(Hash)
24
+ lat = HashWithIndifferentAccess.new(lat_or_coordinate)
25
+ @lat = lat[:latitude].to_f
26
+ @lon = lat[:longitude].to_f
27
+ else
28
+ raise 'Parâmetros inválidos'
29
+ end
30
+
31
+ Float.include Map::Float
32
+ end
33
+
34
+ def bearing_to(point)
35
+ raise 'Point não é do tipo LatLon' unless point.is_a?(Map::LatLonService)
36
+
37
+ _φ1 = self.lat.to_radians
38
+ _φ2 = point.lat.to_radians
39
+ _Δλ = (point.lon - self.lon).to_radians
40
+
41
+ y = Math.sin(_Δλ) * Math.cos(_φ2)
42
+ x = Math.cos(_φ1) * Math.sin(_φ2) - Math.sin(_φ1) * Math.cos(_φ2) * Math.cos(_Δλ)
43
+ θ = Math.atan2(y, x)
44
+
45
+ (θ.to_degrees + 360) % 360
46
+ end
47
+
48
+ def destination_point(distance, bearing)
49
+ radius = 6371e3
50
+
51
+ # _φ2 = asin( sin_φ1⋅cosδ + cos_φ1⋅sinδ⋅cosθ )
52
+ # _λ2 = _λ1 + atan2( sinθ⋅sinδ⋅cos_φ1, cosδ − sin_φ1⋅sin_φ2 )
53
+ # see http://williams.best.vwh.net/avform.htm#LL
54
+
55
+ δ = distance / radius
56
+ θ = bearing.to_f.to_radians
57
+
58
+ _φ1 = self.lat.to_radians
59
+ _λ1 = self.lon.to_radians
60
+
61
+ _φ2 = Math.asin(Math.sin(_φ1)*Math.cos(δ) + Math.cos(_φ1)*Math.sin(δ)*Math.cos(θ))
62
+ x = Math.cos(δ) - Math.sin(_φ1) * Math.sin(_φ2)
63
+ y = Math.sin(θ) * Math.sin(δ) * Math.cos(_φ1)
64
+ _λ2 = _λ1 + Math.atan2(y, x)
65
+
66
+ self.class.new(_φ2.to_degrees, (_λ2.to_degrees + 540) % 360-180)
67
+ end
68
+
69
+ def distance_to(point)
70
+ radius = 6371e3
71
+
72
+ _φ1 = self.lat.to_radians
73
+ _λ1 = self.lon.to_radians
74
+
75
+ _φ2 = point.lat.to_radians
76
+ _λ2 = point.lon.to_radians
77
+
78
+ _Δφ = _φ2 - _φ1
79
+ _Δλ = _λ2 - _λ1
80
+
81
+ a = Math.sin(_Δφ/2) * Math.sin(_Δφ/2) + Math.cos(_φ1) * Math.cos(_φ2) * Math.sin(_Δλ/2) * Math.sin(_Δλ/2)
82
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
83
+
84
+ radius * c
85
+ end
86
+
87
+ def to_hash
88
+ {
89
+ latitude: self.lat,
90
+ longitude: self.lon
91
+ }
92
+ end
93
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::PolygonService
4
+ CACHE_EXPIRES = 1.hours
5
+ POS_LATITUDE = 1
6
+ POS_LONGITUDE = 0
7
+
8
+ def initialize(glebas = nil)
9
+ @glebas = glebas
10
+ end
11
+
12
+ def inside_any_gleba?(point)
13
+ propriedade = inside_any_propriedade?(point)
14
+ talhoes = propriedade&.glebas || glebas
15
+ talhoes.each do |talhao|
16
+ coords = polygon(talhao)
17
+ coords.each do |coord|
18
+ return talhao if point_inside?(point, coord)
19
+ end
20
+ end
21
+
22
+ nil
23
+ end
24
+
25
+ def glebas
26
+ @glebas ||= Gleba.select(:cd_gleba, :cd_propriedade, :arquivo_kml, :descricao).active.with_kml
27
+ end
28
+
29
+ def propriedades
30
+ @propriedades ||= Propriedade.includes(:glebas).select(:cd_propriedade, :descricao).active
31
+ end
32
+
33
+ def inside_polygons?(point, polygons)
34
+ polygons.any? do |polygon|
35
+ point_inside?(point, polygon)
36
+ end
37
+ end
38
+
39
+ def inside_any_propriedade?(point)
40
+ propriedades.each do |propriedade|
41
+ return propriedade if point_inside_bounds?(point, propriedade.bounds)
42
+ end
43
+
44
+ nil
45
+ end
46
+
47
+ private
48
+
49
+ def point_inside_bounds?(point, bounds)
50
+ latitude = (point[:latitude] || point['latitude']).to_f
51
+ longitude = (point[:longitude] || point['longitude']).to_f
52
+
53
+ latitude.to_f.between?(bounds[:south_west][POS_LATITUDE].to_f, bounds[:north_east][POS_LATITUDE].to_f) &&
54
+ longitude.to_f.between?(bounds[:south_west][POS_LONGITUDE].to_f, bounds[:north_east][POS_LONGITUDE].to_f)
55
+ end
56
+
57
+ # Algoritmo portado de código javascript
58
+ # ray-casting algorithm based on
59
+ # http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
60
+ # https://github.com/substack/point-in-polygon
61
+ def point_inside?(point, polygon)
62
+ x = (point[:latitude] || point['latitude']).to_f
63
+ y = (point[:longitude] || point['longitude']).to_f
64
+
65
+ inside = false
66
+ i = 0
67
+ j = polygon.length - 1
68
+
69
+ polygon.length.times do
70
+ xi = (polygon[i][1] || polygon[i]['latitude']).to_f
71
+ yi = (polygon[i][0] || polygon[i]['longitude']).to_f
72
+
73
+ xj = (polygon[j][1] || polygon[j]['latitude']).to_f
74
+ yj = (polygon[j][0] || polygon[j]['longitude']).to_f
75
+
76
+ intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)
77
+
78
+ inside = !inside if intersect
79
+ i += 1
80
+ j = i - 1
81
+ end
82
+
83
+ inside
84
+ end
85
+
86
+ def coordinates(gleba)
87
+ if gleba.respond_to?(:coordinates)
88
+ gleba.coordinates
89
+ elsif gleba.respond_to?(:at)
90
+ gleba.at(:coordinates).text.strip
91
+ else
92
+ Map::KmlService.new(gleba.arquivo_kml).coordinates
93
+ end
94
+ end
95
+
96
+ def polygon(gleba)
97
+ SafeCacheService.new("#{gleba.id}/coordinates", expires_in: CACHE_EXPIRES, execute_if: Rails.env.production?)
98
+ .fetch { coordinates(gleba) }
99
+ end
100
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::RgeoService
4
+ attr_reader :factory
5
+
6
+ def initialize
7
+ @factory = RGeo::Geos.factory(srid: 3361)
8
+ end
9
+
10
+ def create_polygon(points)
11
+ raise 'Parâmetro inválido' unless points.is_a?(Array)
12
+
13
+ transformed = points.collect do |point|
14
+ create_point(point)
15
+ end
16
+
17
+ factory.polygon(factory.linear_ring(transformed))
18
+ end
19
+
20
+ def create_point(point)
21
+ if point.is_a?(Array)
22
+ point.first < point.second ? factory.point(point.second.to_f, point.first.to_f) : factory.point(point.first.to_f, point.second.to_f)
23
+ elsif point.is_a?(Hash)
24
+ factory.point((point['latitude'] || point[:latitude]).to_f, (point['longitude'] || point[:longitude]).to_f)
25
+ end
26
+ end
27
+
28
+ def polygon_inside_any_other?(base, other)
29
+ return false if base.nil? || other.nil?
30
+ return true if base.overlaps?(other) || other.overlaps?(base)
31
+ return true if base.contains?(other) || other.contains?(base)
32
+ end
33
+
34
+ def centroid(points)
35
+ polygon = create_polygon(points)
36
+ centroid = polygon.centroid
37
+ {
38
+ latitude: centroid.x,
39
+ longitude: centroid.y
40
+ }
41
+ end
42
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Converte um shape file para tif
4
+ # 1. Rasteriza o shape
5
+ # 2. Faz resample
6
+ # 3. Aplica escala de cores
7
+ class Map::ShapeToTifService
8
+ attr_reader :options, :shape_file
9
+
10
+ def initialize(shape_file)
11
+ @shape_file = shape_file
12
+ @services = []
13
+ end
14
+
15
+ def call(options)
16
+ @options = options
17
+ color_result = color
18
+ crop_service = Map::Gdal::CropService.new(color_result[:file])
19
+ original_file = color_result[:file]
20
+ color_result[:file] = crop_service.call({
21
+ reference: IO.read(options[:kml]),
22
+ s_srs: 'WGS84'
23
+ })
24
+ FileUtils.rm_rf(original_file)
25
+ color_result
26
+ ensure
27
+ clean_services
28
+ end
29
+
30
+ private
31
+
32
+ def raster
33
+ @services << Map::Gdal::RasterService.new(@shape_file)
34
+ raster_options = {
35
+ a_nodata: 0,
36
+ format: 'tif',
37
+ ot: 'Float',
38
+ a: options[:field],
39
+ ts: options[:ts] || '500 500'
40
+ }
41
+ raster_options[:te] = te if te
42
+ @services.last.call(raster_options)
43
+ end
44
+
45
+ def te
46
+ if options[:te]
47
+ options[:te]
48
+ elsif options[:kml]
49
+ Map::Gdal::OgriInfoService.new(options[:kml]).limits
50
+ end
51
+ end
52
+
53
+ def color
54
+ service = Map::Gdal::ColorizeService.new(resample)
55
+ color_table = options[:color_table] || service.generate_color_table
56
+ {
57
+ colors: colors(color_table),
58
+ json: json,
59
+ file: service.call(color_table: color_table)
60
+ }
61
+ end
62
+
63
+ def resample
64
+ @services << Map::Gdal::TranslateService.new(raster)
65
+ @services.last.call({
66
+ tr: '0.00001 0.00001',
67
+ r: 'cubicspline'
68
+ })
69
+ end
70
+
71
+ def clean_services
72
+ @services.map(&:clean)
73
+ end
74
+
75
+ def json
76
+ service = Map::Gdal::Ogr2ogrService.new(@shape_file)
77
+ out = service.call({
78
+ f: 'GeoJSON',
79
+ select: options[:field]
80
+ })
81
+ parsed = JSON.parse(IO.read(out))
82
+ service.clean
83
+ parsed
84
+ end
85
+
86
+ def colors(color_table)
87
+ Map::Gdal::ColorizeService.format_color_table(color_table)
88
+ end
89
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::TileService
4
+ def initialize(latitude, longitude, zoom)
5
+ @latitude = latitude
6
+ @longitude = longitude
7
+ @zoom = zoom
8
+ end
9
+
10
+ # http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_3
11
+ def points
12
+ lat_rad = @latitude/180 * Math::PI
13
+ squared_zoom = 2.0 ** @zoom
14
+ point_x = ((@longitude + 180.0) / 360.0 * squared_zoom).to_i
15
+ point_y = ((1.0 - Math::log(Math::tan(lat_rad) + (1 / Math::cos(lat_rad))) / Math::PI) / 2.0 * squared_zoom).to_i
16
+
17
+ {
18
+ x: point_x,
19
+ y: point_y
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Map::TilesBase
4
+ def path(folder)
5
+ if Rails.env.development?
6
+ File.join(Dir.pwd, 'public', 'reports', folder)
7
+ else
8
+ File.join('/tmp', 'relatorios', folder)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Map::TrackingCleanerService
4
+ attr_reader :trackings
5
+
6
+ def initialize(trackings)
7
+ @trackings = trackings.to_a
8
+ end
9
+
10
+ def speed
11
+ log('Velocidade') do
12
+ @trackings.select do |tracking|
13
+ tracking.st_acao == 'S' || tracking.speed.to_f > 0
14
+ end
15
+ end
16
+ end
17
+
18
+ def minimum_distance(distance)
19
+ log('Distância') do
20
+ last_valid = nil
21
+ trackings.select do |tracking|
22
+ selected = true
23
+
24
+ atual = Map::LatLonService.new(tracking.latitude, tracking.longitude)
25
+ if last_valid
26
+ tracking.distance = atual.distance_to(last_valid)
27
+ selected = tracking.distance >= distance
28
+ end
29
+ last_valid = Map::LatLonService.new(tracking.latitude, tracking.longitude)
30
+
31
+ selected
32
+ end
33
+ end
34
+ end
35
+
36
+ def accuracy_average
37
+ log('Precisão média') do
38
+ sum_accuracy = trackings.inject(0) do |result, tracking|
39
+ result + tracking.accuracy.to_f
40
+ end
41
+
42
+ if sum_accuracy > 0
43
+ accuracy_average = sum_accuracy / trackings.length
44
+ trackings.select do |tracking|
45
+ tracking.st_acao == 'S' || tracking.accuracy.to_f <= (accuracy_average * 1.5)
46
+ end
47
+ else
48
+ trackings
49
+ end
50
+ end
51
+ end
52
+
53
+ def simplify
54
+ log 'Simplify' do
55
+ points = trackings.map do |point|
56
+ {x: point.latitude.to_f, y: point.longitude.to_f}
57
+ end
58
+
59
+ points = SimplifyRb::Simplifier.new.process(points, 0.00001, false)
60
+
61
+ trackings.select do |point|
62
+ points.find{ |p| p[:x] == point.latitude.to_f && p[:y] == point.longitude.to_f }
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def log(type)
70
+ Rails.logger.info "Antes da limpeza de #{type.green}: #{@trackings.length}".red
71
+
72
+ ret = yield
73
+
74
+ Rails.logger.info "Depois da Limpeza de #{type.green}: #{ret.length}".red
75
+
76
+ self.class.new(ret)
77
+ end
78
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Satellite::ImageryProccessor
4
+ attr_accessor :blue, :green, :red, :near_infrared, :small_thumb, :large_thumb, :info
5
+ attr_accessor :rgb_service, :ndvi_service, :rgb_file, :ndvi_file
6
+
7
+ def initialize(bands_object)
8
+ self.blue = bands_object[:blue]
9
+ self.green = bands_object[:green]
10
+ self.red = bands_object[:red]
11
+ self.near_infrared = bands_object[:near_infrared]
12
+ self.small_thumb = bands_object[:small_thumb]
13
+ self.large_thumb = bands_object[:large_thumb]
14
+ self.info = bands_object[:info]
15
+ end
16
+
17
+ def proccess
18
+ Rails.logger.info 'Criando ndvi service...'
19
+ create_ndvi_service
20
+
21
+ Rails.logger.info 'Criando rgb service...'
22
+ create_rgb_service
23
+
24
+ Rails.logger.info 'Criando registro de Scene...'
25
+ create_scene
26
+ ensure
27
+ Rails.logger.info 'Processamento finalizado.'
28
+ rgb_service.try(:clean)
29
+ ndvi_service.try(:clean)
30
+ `rm -rf #{info[:files_path]}`
31
+ end
32
+
33
+ private
34
+
35
+ def create_rgb_service
36
+ self.rgb_service = Map::Gdal::RgbService.new([red, green, blue])
37
+ self.rgb_file = rgb_service.call
38
+ end
39
+
40
+ def create_ndvi_service
41
+ self.ndvi_service = Map::Gdal::NdviService.new(red, near_infrared)
42
+ self.ndvi_file = ndvi_service.call
43
+ end
44
+
45
+ def create_scene
46
+ warp_service = Map::Gdal::WarpService.new(rgb_file)
47
+ out = warp_service.call(t_srs: '"+proj=longlat +ellps=WGS84"')
48
+ bounds_info = JSON.parse(`gdalinfo #{out} -mm -json`)
49
+ warp_service.clean
50
+
51
+ translate_service = Map::Gdal::TranslateService.new(rgb_file)
52
+
53
+ Scene.create!(
54
+ scene: info[:scene],
55
+ date: info[:date],
56
+ source: info[:source],
57
+ cloud_cover: info[:cloud_cover],
58
+ ndvi: File.open(ndvi_file),
59
+ rgb: File.open(translate_service.to_png(tr: '128 128', a_nodata: '-9999')),
60
+ small_thumb: (File.open(small_thumb) if small_thumb.present?),
61
+ large_thumb: File.open(large_thumb),
62
+ bounds: {
63
+ lower_left: bounds_info['cornerCoordinates']['lowerLeft'].reverse,
64
+ upper_right: bounds_info['cornerCoordinates']['upperRight'].reverse
65
+ }
66
+ )
67
+ ensure
68
+ translate_service&.clean
69
+ end
70
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satellite
4
+ module Landsat8
5
+ class CoordinateConverterService
6
+ include HTTParty
7
+ base_uri 'https://landsatlook.usgs.gov/arcgis/rest/services/LLook_Outlines/MapServer/1'
8
+
9
+ attr_accessor :lat, :lon
10
+
11
+ def initialize(lat, lon)
12
+ self.lat = lat
13
+ self.lon = lon
14
+ end
15
+
16
+ def convert
17
+ response = self.class.get('/query', {
18
+ query: {
19
+ where: "MODE='D'",
20
+ geometry: "#{lon}, #{lat}",
21
+ geometryType: 'esriGeometryPoint',
22
+ spatialRel: 'esriSpatialRelIntersects',
23
+ outFields: '*',
24
+ returnGeometry: false,
25
+ returnTrueCurves: false,
26
+ returnIdsOnly: false,
27
+ returnCountOnly: false,
28
+ returnZ: false,
29
+ returnM: false,
30
+ returnDistinctValues: false,
31
+ f: 'json'
32
+ }.to_param
33
+ })
34
+
35
+ result = JSON.parse(response).dig('features', 0, 'attributes')
36
+ "#{result['PATH'].to_s.rjust(3, '0')}#{result['ROW'].to_s.rjust(3, '0')}"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Satellite LandSat8
4
+ # 30m de resolução
5
+ # Banda 2 = Azul
6
+ # Banda 3 = Verde
7
+ # Banda 4 = Vermelho: 640-690 nm
8
+ # Banda 5 = Infravermelho próximo: 850-880 nm
9
+ module Satellite
10
+ module Landsat8
11
+ class ImageryService
12
+ AWS_URL = 's3://landsat-pds/c1/L8'
13
+
14
+ attr_accessor :scene, :quantity
15
+
16
+ # $scene = { path: '000', row: '000' }
17
+ def initialize(scene, quantity = 2)
18
+ self.scene = "#{scene[:path]}/#{scene[:row]}"
19
+ self.quantity = quantity
20
+ end
21
+
22
+ def download_and_proccess
23
+ Rails.logger.info "[Landsat8 | #{scene}] Listando últimos #{quantity} arquivos..."
24
+ files = last
25
+
26
+ Rails.logger.info "[Landsat8 | #{scene}] Checando arquivos que precisam ser baixados..."
27
+ files.reject!{ |file| scene_already_proccessed?(file) }
28
+
29
+ Rails.logger.info "[Landsat8 | #{scene}] Baixando #{files.length} cenas..."
30
+ files.each_with_index do |file, index|
31
+ Rails.logger.info "[Landsat8 | #{scene}] Fazendo download da cena do dia #{file[:date]}..."
32
+ useful_files(file[:file]).map do |useful_file|
33
+ Thread.new {
34
+ Rails.logger.info "[Landsat8 | #{scene}] Fazendo download do arquivo #{useful_file}..."
35
+ `aws s3 cp #{AWS_URL}/#{scene}/#{file[:file]}/#{useful_file} "#{output_path}/#{file[:file]}/#{useful_file}"`
36
+ raise 'Error to execute aws s3 cp' unless $?.success?
37
+ }
38
+ end.map(&:join)
39
+
40
+ Rails.logger.info "[Landsat8 | #{scene}] Processando arquivos #{index.next}/#{files.length}."
41
+ Satellite::ImageryProccessor.new(band_object(file[:file])).proccess
42
+ end
43
+ end
44
+
45
+ def last
46
+ @lasts ||= `aws s3 ls #{AWS_URL}/#{scene}/`
47
+ .split("\n")
48
+ .select{ |file| file.end_with?('_RT/') } # Seleciona somente REAL TIME files - https://landsat.usgs.gov/landsat-collections
49
+ .map do |name|
50
+ name = name[/.*(LC08.+)\//,1]
51
+ date = name[/.+?_.+?_.+?_(.+?)_/, 1].to_date
52
+ {
53
+ file: name,
54
+ date: date
55
+ }
56
+ end
57
+ .sort_by{ |item| item[:date] }
58
+ .last(quantity)
59
+ .reverse
60
+ end
61
+
62
+ def useful_files(file_path)
63
+ all_files_from_last_scene = `aws s3 ls #{AWS_URL}/#{scene}/#{file_path}/`
64
+
65
+ all_files_from_last_scene.split("\n").inject([]) do |useful_files, current_file|
66
+ filtered_file = current_file[/.*(LC08.+(B[2-5].TIF|.jpg|MTL.txt))$/, 1]
67
+ useful_files << filtered_file if filtered_file.present?
68
+ useful_files
69
+ end.sort_by { |file| file.include?('.txt') && 1 || 2 }
70
+ end
71
+
72
+ def output_path
73
+ return @path if @path.present?
74
+
75
+ @path = File.join(Dir.pwd, 'public', 'system', 'data', 'imagens', 'landsat8')
76
+ FileUtils.mkdir_p(@path)
77
+ @path
78
+ end
79
+
80
+ def scene_already_proccessed?(file)
81
+ Scene
82
+ .landsat8
83
+ .by_date(file[:date])
84
+ .by_scene(scene.delete('/'))
85
+ .exists?
86
+ end
87
+
88
+ def info_object(scene_folder)
89
+ info_content = IO.read(Dir.glob(File.join(output_path, scene_folder, '*.txt')).first)
90
+ @info_object = {
91
+ date: info_content.match(/DATE_ACQUIRED = (.+)/)[1].to_date,
92
+ cloud_cover: info_content.match(/CLOUD_COVER = (.+)/)[1],
93
+ scene: info_content.match(/LANDSAT_PRODUCT_ID = (.+)/)[1].split('_').third,
94
+ source: :landsat8,
95
+ base_path: output_path,
96
+ files_path: "#{output_path}/#{scene_folder}"
97
+ }
98
+ end
99
+
100
+ def band_object(file)
101
+ {
102
+ blue: "#{output_path}/#{file}/#{file}_B2.TIF",
103
+ green: "#{output_path}/#{file}/#{file}_B3.TIF",
104
+ red: "#{output_path}/#{file}/#{file}_B4.TIF",
105
+ near_infrared: "#{output_path}/#{file}/#{file}_B5.TIF",
106
+ small_thumb: "#{output_path}/#{file}/#{file}_thumb_small.jpg",
107
+ large_thumb: "#{output_path}/#{file}/#{file}_thumb_large.jpg",
108
+ info: info_object(file)
109
+ } if File.exist?(File.join(output_path, file))
110
+ end
111
+ end
112
+ end
113
+ end