diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index a7a7d9c4c..c88e4108d 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -19,6 +19,12 @@ module Instructeurs 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 @attestation = dossier.procedure.attestation_template.render_attributes_for(dossier: dossier) diff --git a/app/models/champ.rb b/app/models/champ.rb index 9e2c2cf63..7f2974db0 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -86,6 +86,10 @@ class Champ < ApplicationRecord true end + def stable_id + type_de_champ.stable_id + end + private def needs_dossier_id? diff --git a/app/models/champs/carte_champ.rb b/app/models/champs/carte_champ.rb index 7d54d9880..6bdc54f9e 100644 --- a/app/models/champs/carte_champ.rb +++ b/app/models/champs/carte_champ.rb @@ -74,7 +74,7 @@ class Champs::CarteChamp < Champ def to_feature_collection { type: 'FeatureCollection', - id: type_de_champ.stable_id, + id: stable_id, bbox: bounding_box, features: (legacy_selections_utilisateur + except_selections_utilisateur).map(&:to_feature) } diff --git a/app/models/champs/repetition_champ.rb b/app/models/champs/repetition_champ.rb index 9105000aa..dbad478c4 100644 --- a/app/models/champs/repetition_champ.rb +++ b/app/models/champs/repetition_champ.rb @@ -35,7 +35,7 @@ class Champs::RepetitionChamp < Champ # We have to truncate the label here as spreadsheets have a (30 char) limit on length. def libelle_for_export - str = "(#{type_de_champ.stable_id}) #{libelle}" + str = "(#{stable_id}) #{libelle}" # /\*?[] are invalid Excel worksheet characters ActiveStorage::Filename.new(str.delete('[]*?')).sanitized.truncate(30) end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index b47cc927f..5aeb5c359 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -700,8 +700,36 @@ class Dossier < ApplicationRecord { id: self.id, procedure_libelle: self.procedure.libelle } 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 + 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) if log_operations? DossierOperationLog.create_and_serialize( diff --git a/app/models/geo_area.rb b/app/models/geo_area.rb index 510dc3b75..604a37c3e 100644 --- a/app/models/geo_area.rb +++ b/app/models/geo_area.rb @@ -36,7 +36,14 @@ class GeoArea < ApplicationRecord { type: 'Feature', 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 diff --git a/app/views/instructeurs/dossiers/_header_actions.html.haml b/app/views/instructeurs/dossiers/_header_actions.html.haml index eaa46936f..4a65d92f9 100644 --- a/app/views/instructeurs/dossiers/_header_actions.html.haml +++ b/app/views/instructeurs/dossiers/_header_actions.html.haml @@ -8,6 +8,9 @@ = link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link" %li = 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? %span.dropdown.print-menu-opener diff --git a/app/views/shared/dossiers/editable_champs/_carte.html.haml b/app/views/shared/dossiers/editable_champs/_carte.html.haml index 4399929b6..c7c058056 100644 --- a/app/views/shared/dossiers/editable_champs/_carte.html.haml +++ b/app/views/shared/dossiers/editable_champs/_carte.html.haml @@ -10,4 +10,4 @@ = render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false } = 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' } diff --git a/config/routes.rb b/config/routes.rb index 4e5648a8d..ba024c094 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -316,6 +316,7 @@ Rails.application.routes.draw do resources :dossiers, only: [:show], param: :dossier_id do member do get 'attestation' + get 'geo_data' get 'apercu_attestation' get 'messagerie' get 'annotations-privees' => 'dossiers#annotations_privees' diff --git a/spec/factories/geo_area.rb b/spec/factories/geo_area.rb index 12e87c679..406844ef3 100644 --- a/spec/factories/geo_area.rb +++ b/spec/factories/geo_area.rb @@ -1,8 +1,10 @@ FactoryBot.define do factory :geo_area do - source { GeoArea.sources.fetch(:cadastre) } - numero { '42' } - feuille { 'A11' } + trait :cadastre do + source { GeoArea.sources.fetch(:cadastre) } + numero { '42' } + feuille { 'A11' } + end trait :quartier_prioritaire do source { GeoArea.sources.fetch(:quartier_prioritaire) } @@ -13,5 +15,25 @@ FactoryBot.define do trait :selection_utilisateur do source { GeoArea.sources.fetch(:selection_utilisateur) } 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 diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 996c409bc..1d53de91e 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1234,4 +1234,34 @@ describe Dossier do it { expect(procedure).not_to be_nil } it { expect(procedure.discarded?).to be_truthy } 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 diff --git a/spec/serializers/champ_serializer_spec.rb b/spec/serializers/champ_serializer_spec.rb index 97a6712f4..54af2b224 100644 --- a/spec/serializers/champ_serializer_spec.rb +++ b/spec/serializers/champ_serializer_spec.rb @@ -26,7 +26,7 @@ describe ChampSerializer do context 'when type champ is carte' do let(:champ) { create(:champ_carte, value: value, geo_areas: [geo_area].compact) } 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 { "type" => 'MultiPolygon',