Skip to content

Commit

Permalink
feat(Fmu): Download shapefiles from Admin
Browse files Browse the repository at this point in the history
Allows downloading the FMU's shapefiles.
  • Loading branch information
santostiago committed Oct 21, 2024
1 parent 9465e0e commit 207e7cf
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get install libgdal-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get install libgdal-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get install libgdal-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand All @@ -55,6 +59,7 @@ jobs:
run: |
sudo apt update --fix-missing
sudo apt-get -yqq install gdal-bin
sudo apt-get install libgdal-dev
npm install -g mjml
- name: Set up Ruby
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ gem "pg"
gem "rails", "~> 7.1.3"
gem "rgeo"
gem "rgeo-geojson"
gem "gdal"

# API
gem "jsonapi-resources", "0.9.12"
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ GEM
googleapis-common-protos-types (~> 1.15)
googleauth (~> 1.11)
grpc (~> 1.65)
gdal (3.0.0)
globalid (1.2.1)
activesupport (>= 6.1)
globalize (6.3.0)
Expand Down Expand Up @@ -827,6 +828,7 @@ DEPENDENCIES
email_spec
factory_bot_rails
faker
gdal
globalize
globalize-versioning!
google-cloud-translate
Expand Down
60 changes: 53 additions & 7 deletions app/admin/fmu.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,48 @@
extend BackRedirectable
extend Versionable

menu false

active_admin_paranoia

config.order_clause

controller do
def scoped_collection
end_of_association_chain.includes(:country, :operator)
end

def download_shapefiles(fmus)
file_content = ShapefileService.generate_shapefile(fmus)

filename = "fmus"
filename = fmus.first&.name if fmus.size == 1
filename = filename.gsub(/[^0-9A-Za-z ]/, "")[0..30]
filename += ".zip"

send_data file_content, type: "application/zip", filename: filename, disposition: "attachment"
end
end

scope -> { I18n.t("active_admin.all") }, :all, default: true
scope -> { I18n.t("active_admin.free") }, :filter_by_free_aa

menu false

active_admin_paranoia

config.order_clause
config.batch_actions = true

batch_action :destroy, false
batch_action :download_shapefiles do |ids|
fmus = batch_action_collection.find(ids)
download_shapefiles(fmus)
end

member_action :download_shapefile, method: :get do
fmu = Fmu.find(params[:id])
download_shapefiles([fmu])
end

action_item :download_shapefile, only: :show do
link_to I18n.t("active_admin.fmus_page.download_shapefile"), download_shapefile_admin_fmu_path(fmu), method: :get
end

permit_params :id, :name, :certification_fsc, :certification_pefc,
:certification_olb, :certification_pafc, :certification_fsc_cw, :certification_tlv,
:certification_ls, :esri_shapefiles_zip, :forest_type, :country_id,
Expand All @@ -42,6 +69,19 @@ def scoped_collection
}
end

sidebar "Shapefiles" do
div do
link_to "Download Filtered Shapefiles", download_filtered_shapefiles_admin_fmus_path(
q: params[:q]&.to_unsafe_h
), class: "shapefiles_button"
end
end

collection_action :download_filtered_shapefiles, method: :get do
fmus = Fmu.ransack(params.dig(:q)).result(distinct: true)
download_shapefiles(fmus)
end

csv do
column :id
column :name
Expand Down Expand Up @@ -88,6 +128,7 @@ def scoped_collection
end

index do
selectable_column
column :id, sortable: true
column :name, sortable: true
column :country, sortable: "country_translations.name"
Expand All @@ -100,7 +141,12 @@ def scoped_collection
column "TLV", :certification_tlv
column "LS", :certification_ls

actions
actions defaults: false do |fmu|
item I18n.t("active_admin.fmus_page.download_shapefile"), download_shapefile_admin_fmu_path(fmu), method: :get
item I18n.t("active_admin.view"), admin_fmu_path(fmu)
item I18n.t("active_admin.edit"), edit_admin_fmu_path(fmu)
item I18n.t("active_admin.delete"), admin_fmu_path(fmu), method: :delete, data: {confirm: I18n.t("active_admin.fmus_page.confirm_delete")}
end
end

form do |f|
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/active_admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//= require quality_controls

//= require active_admin/active_admin_globalize

//= require chartkick
//= require Chart.bundle

Expand Down
6 changes: 6 additions & 0 deletions app/assets/stylesheets/active_admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ $to-be-reviewed: #99AA99;
}
}

.shapefiles_button {
@extend .button;
display: block !important;
text-align: center;
}

// To increase the width of observations-details
.col-details {
min-width: 500px;
Expand Down
69 changes: 69 additions & 0 deletions app/services/shapefile_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
require "gdal"
require "rgeo"
require "zip"

class ShapefileService
# Generate a shapefile from a collection of objects that respond to the `geometry` method
# The `geometry` method should return an RGeo::Feature object
def self.generate_shapefile(shapes)
# Create a temporary directory to store the shapefile components
Dir.mktmpdir do |dir|
shapes_name = (shapes.size == 1) ? shapes.first.name : "shapes"

shapefile_path = File.join(dir, "#{shapes_name}.shp")

# Initialize GDAL
driver = Gdal::Ogr.get_driver_by_name("ESRI Shapefile")
datasource = driver.create_data_source(shapefile_path)
layer = datasource.create_layer("shapes", nil, Gdal::Ogr::WKBPOLYGON)

# Define the fields
field_defn = Gdal::Ogr::FieldDefn.new("id", Gdal::Ogr::OFTINTEGER)
layer.create_field(field_defn)
field_defn = Gdal::Ogr::FieldDefn.new("name", Gdal::Ogr::OFTSTRING)
layer.create_field(field_defn)
field_defn = Gdal::Ogr::FieldDefn.new("operator", Gdal::Ogr::OFTSTRING)
layer.create_field(field_defn)

shapes.each do |shape|
feature = Gdal::Ogr::Feature.new(layer.get_layer_defn)
feature.set_field("id", shape.id)
feature.set_field("name", shape.name)
feature.set_field("operator", shape.operator&.name)
geometry = Gdal::Ogr.create_geometry_from_wkt(shape.geometry.as_text)
feature.set_geometry(geometry)
layer.create_feature(feature)
end

datasource.sync_to_disk

# Write the .prj file
prj_content = generate_prj_content
prj_path = File.join(dir, "#{shapes_name}.prj")
File.write(prj_path, prj_content)

# Collect the generated shapefile parts
files = Dir.glob("#{dir}/*")

zipfile_path = File.join(dir, "#{shapes_name}.zip")
Zip::File.open(zipfile_path, Zip::File::CREATE) do |zipfile|
files.each do |file|
zipfile.add(File.basename(file), file)
end
end

# Read the zipfile content
zip_content = File.read(zipfile_path)

# Return the zip content
zip_content
end
end

def self.generate_prj_content
'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,' \
'AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,' \
'AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,' \
'AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]'
end
end
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ en:
min_fine: 'Minimum Fine'
indicator_apv: 'Indicator APV'
law_details: 'Law details'
fmus_page:
download_shapefile: 'Download shapefile'
confirm_delete: 'Are you sure you want to delete this FMU?'
operator_page:
producer: 'Producer'
producer_activated: 'Producer activated'
Expand Down
3 changes: 3 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ fr:
min_fine: 'Amende minimale'
indicator_apv: 'Indicateur APV'
law_details: 'Détails de la loi'
fmus_page:
download_shapefile: 'Télécharger shapefile'
confirm_delete: 'Êtes-vous sûr de vouloir supprimer cette FMU?'
required_operator_document_page:
exists: 'Existe'
publication_authorization: 'Autorisation de publication'
Expand Down
22 changes: 22 additions & 0 deletions spec/services/shapefile_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require "rails_helper"

RSpec.describe ShapefileService do
describe ".generate_shapefile" do
let(:fmu1) { create(:fmu_geojson) }
let(:fmu2) { create(:fmu_geojson) }

it "returns a zip file with the shapefile components" do
file_content = described_class.generate_shapefile([fmu1.reload, fmu2.reload])
zip_file = Tempfile.new("shapes.zip")
zip_file.write(file_content)
zip_file.rewind

Zip::File.open(zip_file) do |zip|
expect(zip.glob("*.shp").count).to eq(1)
expect(zip.glob("*.shx").count).to eq(1)
expect(zip.glob("*.dbf").count).to eq(1)
expect(zip.glob("*.prj").count).to eq(1)
end
end
end
end

0 comments on commit 207e7cf

Please sign in to comment.