-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathconversions.rb
More file actions
125 lines (108 loc) · 3.63 KB
/
Copy pathconversions.rb
File metadata and controls
125 lines (108 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
module RedmineGtt
# for some reason these conversions are not reversible, i.e. when I try to
# convert from json to wkb and back to json I get
#
# RGeo::Error::ParseError: Bad endian byte value: 123
#
# It appears to work when the wkb gets written to / read from database in
# between.
module Conversions
class GeomToJson
# 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)
round_coordinates RGeo::GeoJSON.encode(feature(object, id, properties))
end
def collection_to_json(data)
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
def initialize()
@parser = RGeo::WKRep::WKBParser.new(
support_ewkb: true,
default_srid: 4326
)
end
def self.call(wkb)
new.call wkb
end
def call(wkb)
@parser.parse(wkb)
end
end
def self.to_feature(geometry, properties: {})
geometry = JSON.parse geometry if geometry.is_a?(String)
{
'type' => 'Feature',
'geometry' => geometry,
'properties' => properties
}
end
# Turns database WKB into geometry attribute string
def self.wkb_to_json(wkb, id: nil, properties: nil)
geom_to_json WkbToGeom.(wkb), id: id, properties: properties
end
# turns Rgeo object into GeoJSON
def self.geom_to_json(object, id: nil, properties: nil)
GeomToJson.new.to_json(object, id: id, properties: properties)
end
# Turn geometry attribute string (GeoJSON) into Rgeo object for database
# use
def self.to_geom(geometry)
geojson = JSON.parse(geometry)
RGeo::GeoJSON.decode(
geojson,
json_parser: :json,
geo_factory: RGeo::Cartesian.preferred_factory(has_z_coordinate: true, srid: 4326)
).geometry
end
# Turn geometry attribute string into WKB for database use
def self.to_wkb(geometry)
geojson = JSON.parse(geometry)
feature = RGeo::GeoJSON.decode(geojson, json_parser: :json)
ewkb = RGeo::WKRep::WKBGenerator.new(
type_format: :ewkb,
emit_ewkb_srid: true,
hex_format: true
)
ewkb.generate feature.geometry
rescue JSON::ParserError
# The Gemetry is likely to be already in WKB format
geometry
end
end
end