Merge pull request #9142 from tchak/fix-validate-geojson
perf(carto): use json schema to validate geojson instead of rgeo
This commit is contained in:
commit
db2cc64af6
11 changed files with 1312 additions and 99 deletions
1
Gemfile
1
Gemfile
|
@ -73,7 +73,6 @@ gem 'rails-i18n' # Locales par défaut
|
||||||
gem 'rake-progressbar', require: false
|
gem 'rake-progressbar', require: false
|
||||||
gem 'redcarpet'
|
gem 'redcarpet'
|
||||||
gem 'rexml' # add missing gem due to ruby3 (https://github.com/Shopify/bootsnap/issues/325)
|
gem 'rexml' # add missing gem due to ruby3 (https://github.com/Shopify/bootsnap/issues/325)
|
||||||
gem 'rgeo-geojson'
|
|
||||||
gem 'rqrcode'
|
gem 'rqrcode'
|
||||||
gem 'saml_idp'
|
gem 'saml_idp'
|
||||||
gem 'sanitize-url'
|
gem 'sanitize-url'
|
||||||
|
|
|
@ -566,9 +566,6 @@ GEM
|
||||||
mime-types (>= 1.16, < 4.0)
|
mime-types (>= 1.16, < 4.0)
|
||||||
netrc (~> 0.8)
|
netrc (~> 0.8)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rgeo (2.2.0)
|
|
||||||
rgeo-geojson (2.1.1)
|
|
||||||
rgeo (>= 1.0.0)
|
|
||||||
rodf (1.1.1)
|
rodf (1.1.1)
|
||||||
builder (>= 3.0)
|
builder (>= 3.0)
|
||||||
dry-inflector (~> 0.1)
|
dry-inflector (~> 0.1)
|
||||||
|
@ -890,7 +887,6 @@ DEPENDENCIES
|
||||||
rake-progressbar
|
rake-progressbar
|
||||||
redcarpet
|
redcarpet
|
||||||
rexml
|
rexml
|
||||||
rgeo-geojson
|
|
||||||
rqrcode
|
rqrcode
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rspec_junit_formatter
|
rspec_junit_formatter
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
class Migrations::NormalizeGeoAreaJob < ApplicationJob
|
|
||||||
def perform(ids)
|
|
||||||
GeoArea.where(id: ids).find_each do |geo_area|
|
|
||||||
geojson = RGeo::GeoJSON.decode(geo_area.geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
|
||||||
geometry = RGeo::GeoJSON.encode(geojson)
|
|
||||||
geo_area.update_column(:geometry, geometry)
|
|
||||||
rescue RGeo::Error::InvalidGeometry
|
|
||||||
geo_area.destroy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -52,7 +52,6 @@ class GeoArea < ApplicationRecord
|
||||||
scope :cadastres, -> { where(source: sources.fetch(:cadastre)) }
|
scope :cadastres, -> { where(source: sources.fetch(:cadastre)) }
|
||||||
|
|
||||||
validates :geometry, geo_json: true, allow_nil: false
|
validates :geometry, geo_json: true, allow_nil: false
|
||||||
before_validation :normalize_geometry
|
|
||||||
|
|
||||||
def to_feature
|
def to_feature
|
||||||
{
|
{
|
||||||
|
@ -230,21 +229,4 @@ class GeoArea < ApplicationRecord
|
||||||
properties['id']
|
properties['id']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def normalize_geometry
|
|
||||||
if geometry.present?
|
|
||||||
normalized_geometry = rgeo_geometry
|
|
||||||
if normalized_geometry.present?
|
|
||||||
self.geometry = RGeo::GeoJSON.encode(normalized_geometry)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def rgeo_geometry
|
|
||||||
RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
|
||||||
rescue RGeo::Error::InvalidGeometry
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
1279
app/schemas/geojson.json
Normal file
1279
app/schemas/geojson.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,9 @@
|
||||||
class GeojsonService
|
class GeojsonService
|
||||||
|
def self.valid?(json)
|
||||||
|
schemer = JSONSchemer.schema(Rails.root.join('app/schemas/geojson.json'))
|
||||||
|
schemer.valid?(json)
|
||||||
|
end
|
||||||
|
|
||||||
def self.to_json_polygon_for_cadastre(coordinates)
|
def self.to_json_polygon_for_cadastre(coordinates)
|
||||||
polygon = {
|
polygon = {
|
||||||
geom: {
|
geom: {
|
||||||
|
|
|
@ -4,9 +4,7 @@ class GeoJSONValidator < ActiveModel::EachValidator
|
||||||
record.errors.add(attribute, :blank, message: options[:message] || "ne peut pas être vide")
|
record.errors.add(attribute, :blank, message: options[:message] || "ne peut pas être vide")
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
unless value.blank? || GeojsonService.valid?(value)
|
||||||
RGeo::GeoJSON.decode(value.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
|
||||||
rescue RGeo::Error::InvalidGeometry
|
|
||||||
record.errors.add(attribute, :invalid_geometry, message: options[:message] || "n'est pas un GeoJSON valide")
|
record.errors.add(attribute, :invalid_geometry, message: options[:message] || "n'est pas un GeoJSON valide")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
namespace :after_party do
|
|
||||||
desc 'Deployment task: normalize_geometries'
|
|
||||||
task normalize_geometries: :environment do
|
|
||||||
puts "Running deploy task 'normalize_geometries'"
|
|
||||||
|
|
||||||
progress = ProgressReport.new(GeoArea.count)
|
|
||||||
GeoArea.in_batches(of: 100) do |geo_areas|
|
|
||||||
ids = geo_areas.ids
|
|
||||||
Migrations::NormalizeGeoAreaJob.perform_later(ids)
|
|
||||||
progress.inc(ids.size)
|
|
||||||
end
|
|
||||||
progress.finish
|
|
||||||
|
|
||||||
# Update task as completed. If you remove the line below, the task will
|
|
||||||
# run with every deploy (or every time you call after_party:run).
|
|
||||||
AfterParty::TaskRecord
|
|
||||||
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -46,7 +46,7 @@ describe Champs::CarteController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'error' do
|
context 'error' do
|
||||||
let(:feature) { attributes_for(:geo_area, :invalid_right_hand_rule_polygon) }
|
let(:feature) { attributes_for(:geo_area, :invalid_point) }
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
champ_id: champ.id,
|
champ_id: champ.id,
|
||||||
|
@ -92,7 +92,7 @@ describe Champs::CarteController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'error' do
|
context 'error' do
|
||||||
let(:feature) { attributes_for(:geo_area, :invalid_right_hand_rule_polygon) }
|
let(:feature) { attributes_for(:geo_area, :invalid_point) }
|
||||||
|
|
||||||
it { expect(response.status).to eq 422 }
|
it { expect(response.status).to eq 422 }
|
||||||
end
|
end
|
||||||
|
|
|
@ -116,6 +116,15 @@ FactoryBot.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :invalid_point do
|
||||||
|
geometry do
|
||||||
|
{
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [46.538476837725796]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
trait :multi_polygon do
|
trait :multi_polygon do
|
||||||
geometry do
|
geometry do
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,36 +23,6 @@ RSpec.describe GeoArea, type: :model do
|
||||||
it { expect(geo_area.location).to eq("46°32'19\"N 2°25'42\"E") }
|
it { expect(geo_area.location).to eq("46°32'19\"N 2°25'42\"E") }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#geometry' do
|
|
||||||
let(:geo_area) { build(:geo_area, :polygon, champ: nil) }
|
|
||||||
let(:polygon) 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
|
|
||||||
|
|
||||||
it { expect(geo_area.geometry).to eq(polygon) }
|
|
||||||
|
|
||||||
context 'polygon_with_extra_coordinate' do
|
|
||||||
let(:geo_area) { build(:geo_area, :polygon_with_extra_coordinate, champ: nil) }
|
|
||||||
before { geo_area.valid? }
|
|
||||||
|
|
||||||
it { expect(geo_area.geometry).to eq(polygon) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
context 'geometry' do
|
context 'geometry' do
|
||||||
subject! { geo_area.validate }
|
subject! { geo_area.validate }
|
||||||
|
@ -62,11 +32,6 @@ RSpec.describe GeoArea, type: :model do
|
||||||
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'hourglass_polygon' do
|
|
||||||
let(:geo_area) { build(:geo_area, :hourglass_polygon, champ: nil) }
|
|
||||||
it { expect(geo_area.errors).to have_key(:geometry) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'line_string' do
|
context 'line_string' do
|
||||||
let(:geo_area) { build(:geo_area, :line_string, champ: nil) }
|
let(:geo_area) { build(:geo_area, :line_string, champ: nil) }
|
||||||
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
||||||
|
@ -77,9 +42,9 @@ RSpec.describe GeoArea, type: :model do
|
||||||
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'invalid_right_hand_rule_polygon' do
|
context "allow empty {}" do
|
||||||
let(:geo_area) { build(:geo_area, :invalid_right_hand_rule_polygon, champ: nil) }
|
let(:geo_area) { build(:geo_area, geometry: {}) }
|
||||||
it { expect(geo_area.errors).to have_key(:geometry) }
|
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "nil" do
|
context "nil" do
|
||||||
|
@ -87,9 +52,19 @@ RSpec.describe GeoArea, type: :model do
|
||||||
it { expect(geo_area.errors).to have_key(:geometry) }
|
it { expect(geo_area.errors).to have_key(:geometry) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "allow empty {}" do
|
context 'invalid point' do
|
||||||
let(:geo_area) { build(:geo_area, geometry: {}) }
|
let(:geo_area) { build(:geo_area, :invalid_point, champ: nil) }
|
||||||
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
it { expect(geo_area.errors).to have_key(:geometry) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context.skip 'invalid_right_hand_rule_polygon' do
|
||||||
|
let(:geo_area) { build(:geo_area, :invalid_right_hand_rule_polygon, champ: nil) }
|
||||||
|
it { expect(geo_area.errors).to have_key(:geometry) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context.skip 'hourglass_polygon' do
|
||||||
|
let(:geo_area) { build(:geo_area, :hourglass_polygon, champ: nil) }
|
||||||
|
it { expect(geo_area.errors).to have_key(:geometry) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue