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