Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/views/settings/gtt/_general.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,11 @@
<%= content_tag(:label, l(:label_hide_map_for_invalid_geom)) %>
<%= check_box_tag 'settings[hide_map_for_invalid_geom]', 1, Setting.plugin_redmine_gtt['hide_map_for_invalid_geom'] %>
</p>

<p>
<%= content_tag(:label, l(:label_geojson_precision)) %>
<%= number_field_tag('settings[geojson_precision]',
@settings['geojson_precision'],
min: 0, max: 15, step: 1) %>
<em class="info"><%= l(:text_geojson_precision_info) %></em>
</p>
2 changes: 2 additions & 0 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ de:
field_format: Format-Typ
select_other_gtt_settings: Andere GTT-Einstellungen
label_hide_map_for_invalid_geom: Ausblenden der Ausgabekarte für ungültige Geometrie
label_geojson_precision: "GeoJSON-Koordinatengenauigkeit"
text_geojson_precision_info: "Nachkommastellen der Koordinaten in der GeoJSON-Ausgabe (EPSG:4326). 6 entspricht etwa 0,11 m; kleinere Werte verringern die Datenmenge."
gtt_hide_map_for_invalid_geom_info: Bitte bearbeiten Sie die Anfrage und stellen
Sie die Geometrie ein, um die Karte zu sehen.
label_default_measure_enabled: Zeige Steuerung zum Messen von Länge und Fläche
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ en:

select_other_gtt_settings: "Other GTT settings"
label_hide_map_for_invalid_geom: "Hide issue map for invalid geometry"
label_geojson_precision: "GeoJSON coordinate precision"
text_geojson_precision_info: "Decimal places for coordinates in GeoJSON output (EPSG:4326). 6 is about 0.11 m; lower values shrink the payload."
gtt_hide_map_for_invalid_geom_info: "Please edit the issue and set the geometry
to see the map."

Expand Down
2 changes: 2 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ ja:

select_other_gtt_settings: "GTTのその他の設定"
label_hide_map_for_invalid_geom: "位置情報が登録されていない時に地図を隠す"
label_geojson_precision: "GeoJSON座標の小数点桁数"
text_geojson_precision_info: "GeoJSON出力(EPSG:4326)の座標の小数点以下の桁数。6桁で約0.11m。小さくするとデータ量を削減できます。"
gtt_hide_map_for_invalid_geom_info: "地図を見るにはチケットを編集して位置情報を登録してください。"

select_default_tracker_icon: "トラッカーアイコンを選択:"
Expand Down
5 changes: 4 additions & 1 deletion init.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@
'editable_geometry_types_on_issue_map' => ["Point"],
'enable_geojson_upload_on_issue_map' => false,
'enable_geocoding_on_map' => false,
'hide_map_for_invalid_geom' => false
'hide_map_for_invalid_geom' => false,
# Keep in sync with RedmineGtt::DEFAULT_GEOJSON_PRECISION (literal here to
# avoid autoloading the module during plugin registration).
'geojson_precision' => 6
},
partial: 'settings/gtt/settings'
)
Expand Down
15 changes: 15 additions & 0 deletions lib/redmine_gtt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

module RedmineGtt

# Number of decimal places GeoJSON coordinates are rounded to on output
# (EPSG:4326 degrees, so 6 digits is ~0.11 m, 7 ~1 cm). RGeo's encoder emits
# full float precision and upstream declines to add a precision option, so
# the plugin rounds itself; the default keeps payloads small without losing
# any real-world accuracy. Configurable via plugin settings; clamped so a
# bad value can never break SQL or produce absurd output.
DEFAULT_GEOJSON_PRECISION = 6
GEOJSON_PRECISION_RANGE = (0..15)

def self.geojson_precision
raw = Setting.plugin_redmine_gtt['geojson_precision']
value = Integer(raw, exception: false) || DEFAULT_GEOJSON_PRECISION
value.clamp(GEOJSON_PRECISION_RANGE.min, GEOJSON_PRECISION_RANGE.max)
end

def self.setup_normal_patches
RedmineGtt::Patches::IssuePatch.apply
RedmineGtt::Patches::IssueQueryPatch.apply
Expand Down
36 changes: 32 additions & 4 deletions lib/redmine_gtt/conversions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,53 @@ module RedmineGtt
module Conversions

class GeomToJson
def initialize()
# RGeo's GeoJSON encoder emits full float precision and offers no
# rounding option (see gtt-project/redmine_gtt#7), so coordinates are
# rounded after encoding. Defaults to the configured plugin precision.
def initialize(precision: RedmineGtt.geojson_precision)
@factory = RGeo::GeoJSON::EntityFactory.instance
@precision = precision
end

def to_json(object, id: nil, properties: nil)
RGeo::GeoJSON.encode feature(object, id, properties)
round_coordinates RGeo::GeoJSON.encode(feature(object, id, properties))
end

def collection_to_json(data)
RGeo::GeoJSON.encode @factory.feature_collection(
round_coordinates RGeo::GeoJSON.encode(@factory.feature_collection(
data.map{|object, id, properties| feature(object, id, properties)}
)
))
end

private

def feature(object, id, properties = nil)
@factory.feature object, id, (properties || {})
end

# Walks the encoded GeoJSON and rounds the numbers under any
# "coordinates" key to @precision decimal places, leaving everything
# else (notably "properties") untouched.
def round_coordinates(node)
case node
when Hash
node.each_with_object({}) do |(key, value), result|
result[key] = key == 'coordinates' ? round_numbers(value) : round_coordinates(value)
end
when Array
node.map { |element| round_coordinates(element) }
else
node
end
end

def round_numbers(value)
case value
when Array then value.map { |element| round_numbers(element) }
when Numeric then value.round(@precision)
else value
end
end
end

class WkbToGeom
Expand Down
5 changes: 4 additions & 1 deletion lib/redmine_gtt/patches/geojson_attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ def self.prepended(base)
module ClassMethods

def geojson_attribute_select
"ST_AsGeoJson(#{table_name}.geom) as db_geojson"
# maxdecimaldigits keeps coordinate precision (and payload size) in
# check; RedmineGtt.geojson_precision returns a clamped Integer so
# the interpolation is injection-safe.
"ST_AsGeoJson(#{table_name}.geom, #{RedmineGtt.geojson_precision}) as db_geojson"
end

def array_to_geojson(array, include_properties: false)
Expand Down
7 changes: 6 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ def assert_geojson(json)
end

def assert_equal_coordinates(a, b)
assert_equal a.flatten.map{|f|f.round 5}, b.flatten.map{|f|f.round 5}
# Compare at the precision GeoJSON output is rounded to. Comparing at a
# lower precision than the output uses would double-round boundary values
# (e.g. 135.2528349 -> round(6) 135.252835 -> round(5) 135.25284, while a
# direct round(5) gives 135.25283).
precision = RedmineGtt::DEFAULT_GEOJSON_PRECISION
assert_equal a.flatten.map{|f|f.round precision}, b.flatten.map{|f|f.round precision}
Comment thread
dkastl marked this conversation as resolved.
Outdated
end

def assert_geojson_collection(json)
Expand Down
16 changes: 8 additions & 8 deletions test/unit/issue_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ class IssueTest < GttTest
new_coordinates = old_coordinates.map{|c| c + 0.000000001}
@issue.update_attribute :geojson, point_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal old_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates old_coordinates, @issue.geojson["geometry"]["coordinates"]

new_coordinates = [old_coordinates[0] + 0.2, old_coordinates[1], old_coordinates[2]]
@issue.update_attribute :geojson, point_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal new_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates new_coordinates, @issue.geojson["geometry"]["coordinates"]
end

test 'should ignore small linestring geom changes' do
Expand All @@ -92,18 +92,18 @@ class IssueTest < GttTest
new_coordinates = old_coordinates.map{|c| [c[0] + 0.000000001, c[1] + 0.000000001, c[2]]}
@issue.update_attribute :geojson, linestring_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal old_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates old_coordinates, @issue.geojson["geometry"]["coordinates"]

new_coordinates = old_coordinates.map{|c| [c[0] + 0.2, c[1], c[2]]}
@issue.update_attribute :geojson, linestring_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal new_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates new_coordinates, @issue.geojson["geometry"]["coordinates"]

new_coordinates = old_coordinates.map{|c| [c[0], c[1], c[2]]}
new_coordinates.delete_at(1)
@issue.update_attribute :geojson, linestring_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal new_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates new_coordinates, @issue.geojson["geometry"]["coordinates"]
end

test 'should ignore small polygon geom changes' do
Expand All @@ -116,17 +116,17 @@ class IssueTest < GttTest
new_coordinates = [old_coordinates[0].map{|c| [c[0] + 0.000000001, c[1] + 0.000000001, c[2]]}]
@issue.update_attribute :geojson, polygon_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal old_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates old_coordinates, @issue.geojson["geometry"]["coordinates"]

new_coordinates = [old_coordinates[0].map{|c| [c[0] + 0.2, c[1], c[2]]}]
@issue.update_attribute :geojson, polygon_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal new_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates new_coordinates, @issue.geojson["geometry"]["coordinates"]

new_coordinates = [old_coordinates[0].map{|c| [c[0], c[1], c[2]]}]
new_coordinates[0].insert(2, [135.301041779,34.680969984,0.0])
@issue.update_attribute :geojson, polygon_geojson(new_coordinates)
@issue.instance_variable_set "@geojson", nil
assert_equal new_coordinates, @issue.geojson["geometry"]["coordinates"]
assert_equal_coordinates new_coordinates, @issue.geojson["geometry"]["coordinates"]
end
end
Loading