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