From 59086cc728cc2e705a7d2ac8169afff9195f7c24 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 1 Oct 2020 11:33:15 +0200 Subject: [PATCH] Fix geo length computations --- app/models/geo_area.rb | 4 +-- app/services/geojson_service.rb | 43 +++++++++++++++++++++++++++++++++ spec/models/geo_area_spec.rb | 2 +- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/app/models/geo_area.rb b/app/models/geo_area.rb index bb535d04b..19b16ce21 100644 --- a/app/models/geo_area.rb +++ b/app/models/geo_area.rb @@ -84,8 +84,8 @@ class GeoArea < ApplicationRecord end def length - if line? && RGeo::Geos.supported? - rgeo_geometry.length.round(1) + if line? + GeojsonService.length(geometry.deep_symbolize_keys).round(1) end end diff --git a/app/services/geojson_service.rb b/app/services/geojson_service.rb index 6c2041224..d2a78d884 100644 --- a/app/services/geojson_service.rb +++ b/app/services/geojson_service.rb @@ -24,6 +24,27 @@ class GeojsonService calculate_area(geojson) end + def self.length(geojson) + segment_reduce(geojson, 0) do |previous_value, segment| + coordinates = segment[:geometry][:coordinates] + previous_value + distance(coordinates[0], coordinates[1]) + end + end + + def self.distance(from, to) + coordinates1 = from + coordinates2 = to + d_lat = degrees_to_radians(coordinates2[1] - coordinates1[1]) + d_lon = degrees_to_radians(coordinates2[0] - coordinates1[0]) + lat1 = degrees_to_radians(coordinates1[1]) + lat2 = degrees_to_radians(coordinates2[1]) + + a = (Math.sin(d_lat / 2)**2) + (Math.sin(d_lon / 2)**2) * Math.cos(lat1) * Math.cos(lat2) + + radians = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) + radians * EQUATORIAL_RADIUS + end + def self.calculate_area(geom) total = 0 case geom[:type] @@ -81,7 +102,29 @@ class GeojsonService total end + def self.segment_reduce(geojson, initial_value) + previous_value = initial_value + started = false + coordinates = geojson[:coordinates].dup + from = coordinates.shift + coordinates.each do |to| + current_segment = { type: 'Feature', geometry: { type: 'LineString', coordinates: [from, to] } } + from = to + if started == false && initial_value.blank? + previous_value = current_segment + else + previous_value = yield previous_value, current_segment + end + started = true + end + previous_value + end + def self.rad(num) num * Math::PI / 180 end + + def self.degrees_to_radians(degrees) + rad(degrees % 360) + end end diff --git a/spec/models/geo_area_spec.rb b/spec/models/geo_area_spec.rb index aef3a13de..377b2bf00 100644 --- a/spec/models/geo_area_spec.rb +++ b/spec/models/geo_area_spec.rb @@ -14,7 +14,7 @@ RSpec.describe GeoArea, type: :model do describe '#length' do let(:geo_area) { build(:geo_area, :line_string) } - it { expect(geo_area.length).to eq(30.8) } + it { expect(geo_area.length).to eq(21.2) } end describe '#location' do