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,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