Merge pull request #5069 from tchak/expose-geo-json-endpoint

Allow instructeurs to download a GeoJSON document for a given dossier
This commit is contained in:
Paul Chavard 2020-04-30 16:01:21 +02:00 committed by GitHub
commit 725c387f91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 109 additions and 8 deletions

View file

@ -19,6 +19,12 @@ module Instructeurs
end end
end end
def geo_data
send_data dossier.to_feature_collection.to_json,
type: 'application/json',
filename: "dossier-#{dossier.id}-features.json"
end
def apercu_attestation def apercu_attestation
@attestation = dossier.procedure.attestation_template.render_attributes_for(dossier: dossier) @attestation = dossier.procedure.attestation_template.render_attributes_for(dossier: dossier)

View file

@ -86,6 +86,10 @@ class Champ < ApplicationRecord
true true
end end
def stable_id
type_de_champ.stable_id
end
private private
def needs_dossier_id? def needs_dossier_id?

View file

@ -74,7 +74,7 @@ class Champs::CarteChamp < Champ
def to_feature_collection def to_feature_collection
{ {
type: 'FeatureCollection', type: 'FeatureCollection',
id: type_de_champ.stable_id, id: stable_id,
bbox: bounding_box, bbox: bounding_box,
features: (legacy_selections_utilisateur + except_selections_utilisateur).map(&:to_feature) features: (legacy_selections_utilisateur + except_selections_utilisateur).map(&:to_feature)
} }

View file

@ -35,7 +35,7 @@ class Champs::RepetitionChamp < Champ
# We have to truncate the label here as spreadsheets have a (30 char) limit on length. # We have to truncate the label here as spreadsheets have a (30 char) limit on length.
def libelle_for_export def libelle_for_export
str = "(#{type_de_champ.stable_id}) #{libelle}" str = "(#{stable_id}) #{libelle}"
# /\*?[] are invalid Excel worksheet characters # /\*?[] are invalid Excel worksheet characters
ActiveStorage::Filename.new(str.delete('[]*?')).sanitized.truncate(30) ActiveStorage::Filename.new(str.delete('[]*?')).sanitized.truncate(30)
end end

View file

@ -700,8 +700,36 @@ class Dossier < ApplicationRecord
{ id: self.id, procedure_libelle: self.procedure.libelle } { id: self.id, procedure_libelle: self.procedure.libelle }
end end
def geo_data?
geo_areas.present?
end
def to_feature_collection
{
type: 'FeatureCollection',
id: id,
bbox: bounding_box,
features: geo_areas.map(&:to_feature)
}
end
private private
def geo_areas
champs.includes(:geo_areas).flat_map(&:geo_areas) + champs_private.includes(:geo_areas).flat_map(&:geo_areas)
end
def bounding_box
factory = RGeo::Geographic.simple_mercator_factory
bounding_box = RGeo::Cartesian::BoundingBox.new(factory)
geo_areas.each do |area|
bounding_box.add(area.rgeo_geometry)
end
[bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates)
end
def log_dossier_operation(author, operation, subject = nil) def log_dossier_operation(author, operation, subject = nil)
if log_operations? if log_operations?
DossierOperationLog.create_and_serialize( DossierOperationLog.create_and_serialize(

View file

@ -36,7 +36,14 @@ class GeoArea < ApplicationRecord
{ {
type: 'Feature', type: 'Feature',
geometry: geometry, geometry: geometry,
properties: properties.merge(source: source, area: area, length: length).compact properties: properties.symbolize_keys.merge(
source: source,
area: area,
length: length,
id: id,
champ_id: champ.stable_id,
dossier_id: champ.dossier_id
).compact
} }
end end

View file

@ -8,6 +8,9 @@
= link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link" = link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link"
%li %li
= link_to "Export PDF", instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link" = link_to "Export PDF", instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link"
- if dossier.geo_data?
%li
= link_to "Export GeoJSON", geo_data_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
- if !PiecesJustificativesService.liste_pieces_justificatives(dossier).empty? - if !PiecesJustificativesService.liste_pieces_justificatives(dossier).empty?
%span.dropdown.print-menu-opener %span.dropdown.print-menu-opener

View file

@ -10,4 +10,4 @@
= render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false } = render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false }
= form.hidden_field :value, = form.hidden_field :value,
data: { remote: true, feature_collection_id: champ.type_de_champ.stable_id, url: champs_carte_path(form.index), params: champ_carte_params(champ).to_query, method: 'post' } data: { remote: true, feature_collection_id: champ.stable_id, url: champs_carte_path(form.index), params: champ_carte_params(champ).to_query, method: 'post' }

View file

@ -316,6 +316,7 @@ Rails.application.routes.draw do
resources :dossiers, only: [:show], param: :dossier_id do resources :dossiers, only: [:show], param: :dossier_id do
member do member do
get 'attestation' get 'attestation'
get 'geo_data'
get 'apercu_attestation' get 'apercu_attestation'
get 'messagerie' get 'messagerie'
get 'annotations-privees' => 'dossiers#annotations_privees' get 'annotations-privees' => 'dossiers#annotations_privees'

View file

@ -1,8 +1,10 @@
FactoryBot.define do FactoryBot.define do
factory :geo_area do factory :geo_area do
source { GeoArea.sources.fetch(:cadastre) } trait :cadastre do
numero { '42' } source { GeoArea.sources.fetch(:cadastre) }
feuille { 'A11' } numero { '42' }
feuille { 'A11' }
end
trait :quartier_prioritaire do trait :quartier_prioritaire do
source { GeoArea.sources.fetch(:quartier_prioritaire) } source { GeoArea.sources.fetch(:quartier_prioritaire) }
@ -13,5 +15,25 @@ FactoryBot.define do
trait :selection_utilisateur do trait :selection_utilisateur do
source { GeoArea.sources.fetch(:selection_utilisateur) } source { GeoArea.sources.fetch(:selection_utilisateur) }
end end
trait :polygon do
geometry do
{
"type": "Polygon",
"coordinates": [
[
[2.428439855575562, 46.538476837725796],
[2.4284291267395024, 46.53842148758162],
[2.4282521009445195, 46.53841410755813],
[2.42824137210846, 46.53847314771794],
[2.428284287452698, 46.53847314771794],
[2.428364753723145, 46.538487907747864],
[2.4284291267395024, 46.538491597754714],
[2.428439855575562, 46.538476837725796]
]
]
}
end
end
end end
end end

View file

@ -1234,4 +1234,34 @@ describe Dossier do
it { expect(procedure).not_to be_nil } it { expect(procedure).not_to be_nil }
it { expect(procedure.discarded?).to be_truthy } it { expect(procedure.discarded?).to be_truthy }
end end
describe "to_feature_collection" do
let(:geo_area) { create(:geo_area, :selection_utilisateur, :polygon) }
let(:champ) { create(:champ_carte, geo_areas: [geo_area]) }
let(:dossier) { create(:dossier, champs: [champ]) }
it 'should have all champs carto' do
expect(dossier.to_feature_collection).to eq({
type: 'FeatureCollection',
id: dossier.id,
bbox: [2.428439855575562, 46.538491597754714, 2.42824137210846, 46.53841410755813],
features: [
{
type: 'Feature',
geometry: {
'coordinates' => [[[2.428439855575562, 46.538476837725796], [2.4284291267395024, 46.53842148758162], [2.4282521009445195, 46.53841410755813], [2.42824137210846, 46.53847314771794], [2.428284287452698, 46.53847314771794], [2.428364753723145, 46.538487907747864], [2.4284291267395024, 46.538491597754714], [2.428439855575562, 46.538476837725796]]],
'type' => 'Polygon'
},
properties: {
area: 219.0,
champ_id: champ.stable_id,
dossier_id: dossier.id,
id: geo_area.id,
source: 'selection_utilisateur'
}
}
]
})
end
end
end end

View file

@ -26,7 +26,7 @@ describe ChampSerializer do
context 'when type champ is carte' do context 'when type champ is carte' do
let(:champ) { create(:champ_carte, value: value, geo_areas: [geo_area].compact) } let(:champ) { create(:champ_carte, value: value, geo_areas: [geo_area].compact) }
let(:value) { nil } let(:value) { nil }
let(:geo_area) { create(:geo_area, geometry: geo_json) } let(:geo_area) { create(:geo_area, :cadastre, geometry: geo_json) }
let(:geo_json) do let(:geo_json) do
{ {
"type" => 'MultiPolygon', "type" => 'MultiPolygon',