fix(geometry): implement our own bbox to replace rgeo
This commit is contained in:
parent
3c585df3ec
commit
8b74a6f39b
10 changed files with 93 additions and 39 deletions
|
@ -12,7 +12,7 @@ module Types
|
||||||
|
|
||||||
global_id_field :id
|
global_id_field :id
|
||||||
field :source, GeoAreaSource, null: false
|
field :source, GeoAreaSource, null: false
|
||||||
field :geometry, Types::GeoJSON, null: false, method: :safe_geometry
|
field :geometry, Types::GeoJSON, null: false
|
||||||
field :description, String, null: true
|
field :description, String, null: true
|
||||||
|
|
||||||
definition_methods do
|
definition_methods do
|
||||||
|
|
|
@ -64,21 +64,14 @@ class Champs::CarteChamp < Champ
|
||||||
end
|
end
|
||||||
|
|
||||||
def bounding_box
|
def bounding_box
|
||||||
factory = RGeo::Geographic.simple_mercator_factory
|
|
||||||
bounding_box = RGeo::Cartesian::BoundingBox.new(factory)
|
|
||||||
|
|
||||||
if geo_areas.present?
|
if geo_areas.present?
|
||||||
geo_areas.filter_map(&:rgeo_geometry).each do |geometry|
|
GeojsonService.bbox(type: 'FeatureCollection', features: geo_areas.map(&:to_feature))
|
||||||
bounding_box.add(geometry)
|
|
||||||
end
|
|
||||||
elsif dossier.present?
|
elsif dossier.present?
|
||||||
point = dossier.geo_position
|
point = dossier.geo_position
|
||||||
bounding_box.add(factory.point(point[:lon], point[:lat]))
|
GeojsonService.bbox(type: 'Feature', geometry: { type: 'Point', coordinates: [point[:lon], point[:lat]] })
|
||||||
else
|
else
|
||||||
bounding_box.add(factory.point(DEFAULT_LON, DEFAULT_LAT))
|
GeojsonService.bbox(type: 'Feature', geometry: { type: 'Point', coordinates: [DEFAULT_LON, DEFAULT_LAT] })
|
||||||
end
|
end
|
||||||
|
|
||||||
[bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_feature_collection
|
def to_feature_collection
|
||||||
|
|
|
@ -1312,14 +1312,7 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def bounding_box
|
def bounding_box
|
||||||
factory = RGeo::Geographic.simple_mercator_factory
|
GeojsonService.bbox(type: 'FeatureCollection', features: geo_areas.map(&:to_feature))
|
||||||
bounding_box = RGeo::Cartesian::BoundingBox.new(factory)
|
|
||||||
|
|
||||||
geo_areas.filter_map(&:rgeo_geometry).each do |geometry|
|
|
||||||
bounding_box.add(geometry)
|
|
||||||
end
|
|
||||||
|
|
||||||
[bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def log_dossier_operation(author, operation, subject = nil)
|
def log_dossier_operation(author, operation, subject = nil)
|
||||||
|
|
|
@ -52,11 +52,12 @@ class GeoArea < ApplicationRecord
|
||||||
scope :cadastres, -> { where(source: sources.fetch(:cadastre)) }
|
scope :cadastres, -> { where(source: sources.fetch(:cadastre)) }
|
||||||
|
|
||||||
validates :geometry, geo_json: true, allow_blank: false
|
validates :geometry, geo_json: true, allow_blank: false
|
||||||
|
before_validation :normalize_geometry
|
||||||
|
|
||||||
def to_feature
|
def to_feature
|
||||||
{
|
{
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
geometry: safe_geometry,
|
geometry: geometry.deep_symbolize_keys,
|
||||||
properties: cadastre_properties.merge(
|
properties: cadastre_properties.merge(
|
||||||
source: source,
|
source: source,
|
||||||
area: area,
|
area: area,
|
||||||
|
@ -96,16 +97,6 @@ class GeoArea < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def safe_geometry
|
|
||||||
RGeo::GeoJSON.encode(rgeo_geometry)
|
|
||||||
end
|
|
||||||
|
|
||||||
def rgeo_geometry
|
|
||||||
RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
|
||||||
rescue RGeo::Error::InvalidGeometry
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def area
|
def area
|
||||||
if polygon?
|
if polygon?
|
||||||
GeojsonService.area(geometry.deep_symbolize_keys).round(1)
|
GeojsonService.area(geometry.deep_symbolize_keys).round(1)
|
||||||
|
@ -120,7 +111,7 @@ class GeoArea < ApplicationRecord
|
||||||
|
|
||||||
def location
|
def location
|
||||||
if point?
|
if point?
|
||||||
Geo::Coord.new(*rgeo_geometry.coordinates.reverse).to_s
|
Geo::Coord.new(*geometry['coordinates'].reverse).to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -238,4 +229,21 @@ 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
|
||||||
|
|
|
@ -13,7 +13,7 @@ class ChampSerializer < ActiveModel::Serializer
|
||||||
def value
|
def value
|
||||||
case object
|
case object
|
||||||
when GeoArea
|
when GeoArea
|
||||||
object.safe_geometry
|
object.geometry
|
||||||
else
|
else
|
||||||
object.for_api
|
object.for_api
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ class GeoAreaSerializer < ActiveModel::Serializer
|
||||||
attribute :code_arr, if: :include_cadastre?
|
attribute :code_arr, if: :include_cadastre?
|
||||||
|
|
||||||
def geometry
|
def geometry
|
||||||
object.safe_geometry
|
object.geometry
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_cadastre?
|
def include_cadastre?
|
||||||
|
|
|
@ -45,6 +45,66 @@ class GeojsonService
|
||||||
radians * EQUATORIAL_RADIUS
|
radians * EQUATORIAL_RADIUS
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.bbox(geojson)
|
||||||
|
result = [-Float::INFINITY, -Float::INFINITY, Float::INFINITY, Float::INFINITY]
|
||||||
|
|
||||||
|
self.coord_each(geojson) do |coord|
|
||||||
|
if result[3] > coord[1]
|
||||||
|
result[3] = coord[1]
|
||||||
|
end
|
||||||
|
if result[2] > coord[0]
|
||||||
|
result[2] = coord[0]
|
||||||
|
end
|
||||||
|
if result[1] < coord[1]
|
||||||
|
result[1] = coord[1]
|
||||||
|
end
|
||||||
|
if result[0] < coord[0]
|
||||||
|
result[0] = coord[0]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.coord_each(geojson)
|
||||||
|
geometries = if geojson.fetch(:type) == "FeatureCollection"
|
||||||
|
geojson.fetch(:features).map { _1.fetch(:geometry) }
|
||||||
|
else
|
||||||
|
[geojson.fetch(:geometry)]
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
geometries.each do |geometry|
|
||||||
|
geometries = if geometry.fetch(:type) == "GeometryCollection"
|
||||||
|
geometry.fetch(:geometries)
|
||||||
|
else
|
||||||
|
[geometry]
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
geometries.each do |geometry|
|
||||||
|
case geometry.fetch(:type)
|
||||||
|
when "Point"
|
||||||
|
yield geometry.fetch(:coordinates).map(&:to_f)
|
||||||
|
when "LineString", "MultiPoint"
|
||||||
|
geometry.fetch(:coordinates).each { yield _1.map(&:to_f) }
|
||||||
|
when "Polygon", "MultiLineString"
|
||||||
|
geometry.fetch(:coordinates).each do |shapes|
|
||||||
|
shapes.each { yield _1.map(&:to_f) }
|
||||||
|
end
|
||||||
|
when "MultiPolygon"
|
||||||
|
geometry.fetch(:coordinates).each do |polygons|
|
||||||
|
polygons.each do |shapes|
|
||||||
|
shapes.each { yield _1.map(&:to_f) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when "GeometryCollection"
|
||||||
|
geometry.fetch(:geometries).each do |geometry|
|
||||||
|
coord_each(geometry) { yield _1 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.calculate_area(geom)
|
def self.calculate_area(geom)
|
||||||
total = 0
|
total = 0
|
||||||
case geom[:type]
|
case geom[:type]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
describe Champs::CarteChamp do
|
describe Champs::CarteChamp do
|
||||||
let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas, type_de_champ: create(:type_de_champ_carte)) }
|
let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas, type_de_champ: create(:type_de_champ_carte)) }
|
||||||
let(:value) { '' }
|
let(:value) { '' }
|
||||||
let(:coordinates) { [[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]] }
|
let(:coordinates) { [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]]] }
|
||||||
let(:geo_json) do
|
let(:geo_json) do
|
||||||
{
|
{
|
||||||
"type" => 'Polygon',
|
"type" => 'Polygon',
|
||||||
|
|
|
@ -1516,8 +1516,8 @@ describe Dossier do
|
||||||
{
|
{
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
geometry: {
|
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]]],
|
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'
|
type: 'Polygon'
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
area: 103.6,
|
area: 103.6,
|
||||||
|
|
|
@ -23,7 +23,7 @@ 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 '#rgeo_geometry' do
|
describe '#geometry' do
|
||||||
let(:geo_area) { build(:geo_area, :polygon, champ: nil) }
|
let(:geo_area) { build(:geo_area, :polygon, champ: nil) }
|
||||||
let(:polygon) do
|
let(:polygon) do
|
||||||
{
|
{
|
||||||
|
@ -47,9 +47,9 @@ RSpec.describe GeoArea, type: :model do
|
||||||
|
|
||||||
context 'polygon_with_extra_coordinate' do
|
context 'polygon_with_extra_coordinate' do
|
||||||
let(:geo_area) { build(:geo_area, :polygon_with_extra_coordinate, champ: nil) }
|
let(:geo_area) { build(:geo_area, :polygon_with_extra_coordinate, champ: nil) }
|
||||||
|
before { geo_area.valid? }
|
||||||
|
|
||||||
it { expect(geo_area.geometry).not_to eq(polygon) }
|
it { expect(geo_area.geometry).to eq(polygon) }
|
||||||
it { expect(geo_area.safe_geometry).to eq(polygon) }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue