rgeo 2.3.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/README.md +23 -14
- data/ext/geos_c_impl/analysis.c +30 -25
- data/ext/geos_c_impl/analysis.h +8 -7
- data/ext/geos_c_impl/coordinates.c +27 -21
- data/ext/geos_c_impl/coordinates.h +5 -2
- data/ext/geos_c_impl/errors.c +19 -10
- data/ext/geos_c_impl/errors.h +11 -4
- data/ext/geos_c_impl/extconf.rb +42 -28
- data/ext/geos_c_impl/factory.c +540 -451
- data/ext/geos_c_impl/factory.h +105 -95
- data/ext/geos_c_impl/geometry.c +593 -387
- data/ext/geos_c_impl/geometry.h +10 -5
- data/ext/geos_c_impl/geometry_collection.c +306 -339
- data/ext/geos_c_impl/geometry_collection.h +6 -20
- data/ext/geos_c_impl/globals.c +169 -0
- data/ext/geos_c_impl/globals.h +46 -0
- data/ext/geos_c_impl/line_string.c +271 -231
- data/ext/geos_c_impl/line_string.h +5 -8
- data/ext/geos_c_impl/main.c +16 -16
- data/ext/geos_c_impl/point.c +65 -67
- data/ext/geos_c_impl/point.h +4 -7
- data/ext/geos_c_impl/polygon.c +137 -135
- data/ext/geos_c_impl/polygon.h +11 -11
- data/ext/geos_c_impl/preface.h +16 -10
- data/ext/geos_c_impl/ruby_more.c +67 -0
- data/ext/geos_c_impl/ruby_more.h +25 -0
- data/lib/rgeo/cartesian/analysis.rb +5 -3
- data/lib/rgeo/cartesian/bounding_box.rb +74 -79
- data/lib/rgeo/cartesian/calculations.rb +64 -33
- data/lib/rgeo/cartesian/factory.rb +57 -102
- data/lib/rgeo/cartesian/feature_classes.rb +68 -46
- data/lib/rgeo/cartesian/feature_methods.rb +67 -25
- data/lib/rgeo/cartesian/interface.rb +6 -41
- data/lib/rgeo/cartesian/planar_graph.rb +373 -0
- data/lib/rgeo/cartesian/sweepline_intersector.rb +147 -0
- data/lib/rgeo/cartesian/valid_op.rb +69 -0
- data/lib/rgeo/cartesian.rb +3 -0
- data/lib/rgeo/coord_sys/cs/entities.rb +303 -99
- data/lib/rgeo/coord_sys/cs/factories.rb +0 -2
- data/lib/rgeo/coord_sys/cs/wkt_parser.rb +90 -42
- data/lib/rgeo/coord_sys.rb +1 -20
- data/lib/rgeo/error.rb +15 -0
- data/lib/rgeo/feature/curve.rb +0 -11
- data/lib/rgeo/feature/factory.rb +26 -36
- data/lib/rgeo/feature/factory_generator.rb +6 -14
- data/lib/rgeo/feature/geometry.rb +146 -66
- data/lib/rgeo/feature/geometry_collection.rb +16 -9
- data/lib/rgeo/feature/line_string.rb +4 -5
- data/lib/rgeo/feature/linear_ring.rb +0 -1
- data/lib/rgeo/feature/multi_curve.rb +0 -6
- data/lib/rgeo/feature/multi_surface.rb +3 -4
- data/lib/rgeo/feature/point.rb +4 -5
- data/lib/rgeo/feature/polygon.rb +1 -2
- data/lib/rgeo/feature/surface.rb +3 -4
- data/lib/rgeo/feature/types.rb +69 -85
- data/lib/rgeo/geographic/factory.rb +98 -125
- data/lib/rgeo/geographic/interface.rb +69 -166
- data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
- data/lib/rgeo/geographic/projected_feature_methods.rb +67 -42
- data/lib/rgeo/geographic/projected_window.rb +36 -22
- data/lib/rgeo/geographic/{proj4_projector.rb → projector.rb} +3 -5
- data/lib/rgeo/geographic/simple_mercator_projector.rb +26 -25
- data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
- data/lib/rgeo/geographic/spherical_feature_methods.rb +86 -9
- data/lib/rgeo/geographic/spherical_math.rb +17 -20
- data/lib/rgeo/geographic.rb +1 -1
- data/lib/rgeo/geos/capi_factory.rb +87 -158
- data/lib/rgeo/geos/capi_feature_classes.rb +50 -36
- data/lib/rgeo/geos/ffi_factory.rb +105 -173
- data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
- data/lib/rgeo/geos/ffi_feature_methods.rb +105 -127
- data/lib/rgeo/geos/interface.rb +20 -59
- data/lib/rgeo/geos/utils.rb +5 -5
- data/lib/rgeo/geos/zm_factory.rb +53 -95
- data/lib/rgeo/geos/zm_feature_methods.rb +30 -33
- data/lib/rgeo/geos.rb +8 -8
- data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +9 -22
- data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -2
- data/lib/rgeo/impl_helper/basic_line_string_methods.rb +28 -56
- data/lib/rgeo/impl_helper/basic_point_methods.rb +2 -14
- data/lib/rgeo/impl_helper/basic_polygon_methods.rb +17 -26
- data/lib/rgeo/impl_helper/utils.rb +21 -0
- data/lib/rgeo/impl_helper/valid_op.rb +350 -0
- data/lib/rgeo/impl_helper/validity_check.rb +139 -0
- data/lib/rgeo/impl_helper.rb +1 -0
- data/lib/rgeo/version.rb +1 -1
- data/lib/rgeo/wkrep/wkb_generator.rb +73 -63
- data/lib/rgeo/wkrep/wkb_parser.rb +33 -31
- data/lib/rgeo/wkrep/wkt_generator.rb +52 -45
- data/lib/rgeo/wkrep/wkt_parser.rb +48 -35
- data/lib/rgeo.rb +1 -3
- metadata +50 -13
- data/lib/rgeo/coord_sys/srs_database/entry.rb +0 -107
- data/lib/rgeo/coord_sys/srs_database/sr_org.rb +0 -64
- data/lib/rgeo/coord_sys/srs_database/url_reader.rb +0 -65
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RGeo
|
4
|
+
module ImplHelper
|
5
|
+
# This helper enforces valid geometry computation, avoiding results such
|
6
|
+
# as a 0 area for a bowtie shaped polygon. Implementations that are part
|
7
|
+
# of RGeo core should all include this.
|
8
|
+
#
|
9
|
+
# You can play around validity checks if needed:
|
10
|
+
#
|
11
|
+
# - {check_validity!} is the method that will raise if your geometry is
|
12
|
+
# not valid. Its message will be the same as {invalid_reason}.
|
13
|
+
# - {make_valid} is the method you can call to get a valid copy of the
|
14
|
+
# current geometry.
|
15
|
+
# - finally, you can bypass any checked method by prepending `unsafe_` to
|
16
|
+
# it. At your own risk.
|
17
|
+
module ValidityCheck
|
18
|
+
# Every method that should not be overriden by the validity check.
|
19
|
+
# Those methods are either accessors or very basic methods not related
|
20
|
+
# to validity checks, or are used to check validity, in which case the
|
21
|
+
# `true/false` gives a correct information, no need to raise).
|
22
|
+
UNCHECKED_METHODS = [
|
23
|
+
# Basic methods
|
24
|
+
:factory, :geometry_type, :as_text, :as_binary, :srid,
|
25
|
+
:dimension, :coordinate_dimension, :spatial_dimension,
|
26
|
+
# Tests
|
27
|
+
:simple?, :closed?, :empty?, :is_3d?, :measured?,
|
28
|
+
# Accessors
|
29
|
+
:exterior_ring, :interior_rings, :[], :num_geometries, :num_interior_rings,
|
30
|
+
:geometry_n, :each, :points, :point_n, :start_point, :end_point, :x, :y, :z, :m,
|
31
|
+
# Trivial methods
|
32
|
+
:num_points, :locate_along, :locate_between,
|
33
|
+
# Comparison
|
34
|
+
:equals?, :rep_equals?, :eql?, :==, :"!="
|
35
|
+
].freeze
|
36
|
+
private_constant :UNCHECKED_METHODS
|
37
|
+
|
38
|
+
# Since methods have their unsafe_ counter part, it means that the `+`
|
39
|
+
# method would lead to having an `unsafe_+` method that is not simply
|
40
|
+
# callable. Here's a simple fallback:
|
41
|
+
SYMBOL2NAME = {
|
42
|
+
:+ => "add",
|
43
|
+
:- => "remove",
|
44
|
+
:* => "multiply"
|
45
|
+
}.tap { |h| h.default_proc = ->(_, key) { key.to_s } }.freeze
|
46
|
+
private_constant :SYMBOL2NAME
|
47
|
+
|
48
|
+
class << self
|
49
|
+
# Note for contributors: this should be called after all methods
|
50
|
+
# are loaded for a given feature classe. No worries though, this
|
51
|
+
# is tested.
|
52
|
+
def override_classes # :nodoc:
|
53
|
+
# Using pop here to be thread safe.
|
54
|
+
while (klass = classes.pop)
|
55
|
+
override(klass)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def included(klass) # :nodoc:
|
60
|
+
classes << klass
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def classes
|
66
|
+
@classes ||= []
|
67
|
+
end
|
68
|
+
|
69
|
+
def override(klass)
|
70
|
+
methods_to_check = feature_methods(klass)
|
71
|
+
|
72
|
+
klass.class_eval do
|
73
|
+
methods_to_check.each do |method_sym|
|
74
|
+
copy = "unsafe_#{SYMBOL2NAME[method_sym]}".to_sym
|
75
|
+
alias_method copy, method_sym
|
76
|
+
undef_method method_sym
|
77
|
+
define_method(method_sym) do |*args|
|
78
|
+
check_validity!
|
79
|
+
args.each do |arg|
|
80
|
+
arg.check_validity! if RGeo::Feature::Geometry.check_type(arg)
|
81
|
+
end
|
82
|
+
method(copy).call(*args)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def feature_methods(klass)
|
89
|
+
feature_defs = Set.new
|
90
|
+
klass
|
91
|
+
.ancestors
|
92
|
+
.select { |ancestor| ancestor <= RGeo::Feature::Geometry }
|
93
|
+
.each { |ancestor| feature_defs.merge(ancestor.instance_methods(false)) }
|
94
|
+
feature_defs & klass.instance_methods - UNCHECKED_METHODS
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Raises {invalid_reason} if the polygon is not valid, does nothing
|
99
|
+
# otherwise.
|
100
|
+
def check_validity!
|
101
|
+
# This method will use a cached invalid_reason for performance purposes.
|
102
|
+
# DO NOT MUTATE GEOMETRIES.
|
103
|
+
return unless invalid_reason_memo
|
104
|
+
|
105
|
+
raise Error::InvalidGeometry, invalid_reason_memo
|
106
|
+
end
|
107
|
+
|
108
|
+
# Tell why the geometry is not valid, `nil` means it is valid.
|
109
|
+
def invalid_reason
|
110
|
+
if defined?(super) == "super"
|
111
|
+
raise Error::RGeoError, "ValidityCheck MUST be loaded before " \
|
112
|
+
"definition of #{self.class}##{__method__}."
|
113
|
+
end
|
114
|
+
|
115
|
+
raise Error::UnsupportedOperation, "Method #{self.class}##{__method__} not defined."
|
116
|
+
end
|
117
|
+
|
118
|
+
# Try and make the geometry valid, this may change its shape.
|
119
|
+
# Returns a valid copy of the geometry.
|
120
|
+
def make_valid
|
121
|
+
if defined?(super) == "super"
|
122
|
+
raise Error::RGeoError, "ValidityCheck MUST be loaded before " \
|
123
|
+
"definition of #{self.class}##{__method__}."
|
124
|
+
end
|
125
|
+
|
126
|
+
raise Error::UnsupportedOperation, "Method #{self.class}##{__method__} not defined."
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def invalid_reason_memo
|
132
|
+
# `defined?` is a bit faster than `instance_variable_defined?`.
|
133
|
+
return @invalid_reason_memo if defined?(@invalid_reason_memo)
|
134
|
+
|
135
|
+
@invalid_reason_memo = invalid_reason
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/rgeo/impl_helper.rb
CHANGED
data/lib/rgeo/version.rb
CHANGED
@@ -39,7 +39,6 @@ module RGeo
|
|
39
39
|
# [<tt>:little_endian</tt>]
|
40
40
|
# If true, output little endian (NDR) byte order. If false, output
|
41
41
|
# big endian (XDR), or network byte order. Default is false.
|
42
|
-
|
43
42
|
class WKBGenerator
|
44
43
|
# :stopdoc:
|
45
44
|
TYPE_CODES = {
|
@@ -100,103 +99,114 @@ module RGeo
|
|
100
99
|
def generate(obj)
|
101
100
|
factory = obj.factory
|
102
101
|
if @type_format == :ewkb || @type_format == :wkb12
|
103
|
-
|
104
|
-
|
102
|
+
has_z = factory.property(:has_z_coordinate)
|
103
|
+
has_m = factory.property(:has_m_coordinate)
|
105
104
|
else
|
106
|
-
|
107
|
-
|
105
|
+
has_z = false
|
106
|
+
has_m = false
|
108
107
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
finish_emitter
|
108
|
+
result = Result.new(has_z, has_m)
|
109
|
+
generate_feature(obj, result, toplevel: true)
|
110
|
+
result.emit(@hex_format)
|
113
111
|
end
|
114
112
|
|
115
113
|
private
|
116
114
|
|
117
|
-
|
118
|
-
|
115
|
+
class Result
|
116
|
+
def initialize(has_z, has_m)
|
117
|
+
@buffer = []
|
118
|
+
@has_z = has_z
|
119
|
+
@has_m = has_m
|
120
|
+
end
|
121
|
+
|
122
|
+
def <<(data)
|
123
|
+
@buffer << data
|
124
|
+
end
|
125
|
+
|
126
|
+
def emit(hex_format)
|
127
|
+
str = @buffer.join
|
128
|
+
hex_format ? str.unpack1("H*") : str
|
129
|
+
end
|
130
|
+
|
131
|
+
def z?
|
132
|
+
@has_z
|
133
|
+
end
|
134
|
+
|
135
|
+
def m?
|
136
|
+
@has_m
|
137
|
+
end
|
138
|
+
end
|
139
|
+
private_constant :Result
|
140
|
+
|
141
|
+
def emit_byte(value, rval)
|
142
|
+
rval << [value].pack("C")
|
119
143
|
end
|
120
144
|
|
121
|
-
def emit_integer(value)
|
122
|
-
|
145
|
+
def emit_integer(value, rval)
|
146
|
+
rval << [value].pack(@little_endian ? "V" : "N")
|
123
147
|
end
|
124
148
|
|
125
|
-
def emit_doubles(array)
|
126
|
-
|
149
|
+
def emit_doubles(array, rval)
|
150
|
+
rval << array.pack(@little_endian ? "E*" : "G*")
|
127
151
|
end
|
128
152
|
|
129
|
-
def emit_line_string_coords(obj)
|
153
|
+
def emit_line_string_coords(obj, rval)
|
130
154
|
array = []
|
131
|
-
obj.points.each { |
|
132
|
-
emit_integer(obj.num_points)
|
133
|
-
emit_doubles(array)
|
155
|
+
obj.points.each { |pt| point_coords(pt, rval, array) }
|
156
|
+
emit_integer(obj.num_points, rval)
|
157
|
+
emit_doubles(array, rval)
|
134
158
|
end
|
135
159
|
|
136
|
-
def point_coords(obj, array = [])
|
160
|
+
def point_coords(obj, rval, array = [])
|
137
161
|
array << obj.x
|
138
162
|
array << obj.y
|
139
|
-
array << obj.z if
|
140
|
-
array << obj.m if
|
163
|
+
array << obj.z if rval.z?
|
164
|
+
array << obj.m if rval.m?
|
141
165
|
array
|
142
166
|
end
|
143
167
|
|
144
|
-
def
|
145
|
-
@
|
146
|
-
end
|
147
|
-
|
148
|
-
def finish_emitter
|
149
|
-
str = @cur_array.join
|
150
|
-
@cur_array = nil
|
151
|
-
@hex_format ? str.unpack("H*")[0] : str
|
152
|
-
end
|
153
|
-
|
154
|
-
def generate_feature(obj, toplevel = false)
|
155
|
-
emit_byte(@little_endian ? 1 : 0)
|
168
|
+
def generate_feature(obj, rval, toplevel: false)
|
169
|
+
emit_byte(@little_endian ? 1 : 0, rval)
|
156
170
|
type = obj.geometry_type
|
157
171
|
type_code = TYPE_CODES[type]
|
158
|
-
unless type_code
|
159
|
-
raise Error::ParseError, "Unrecognized Geometry Type: #{type}"
|
160
|
-
end
|
172
|
+
raise Error::ParseError, "Unrecognized Geometry Type: #{type}" unless type_code
|
161
173
|
emit_srid = false
|
162
|
-
|
163
|
-
|
164
|
-
type_code |=
|
174
|
+
case @type_format
|
175
|
+
when :ewkb
|
176
|
+
type_code |= 0x80000000 if rval.z?
|
177
|
+
type_code |= 0x40000000 if rval.m?
|
165
178
|
if @emit_ewkb_srid && toplevel
|
166
179
|
type_code |= 0x20000000
|
167
180
|
emit_srid = true
|
168
181
|
end
|
169
|
-
|
170
|
-
type_code += 1000 if
|
171
|
-
type_code += 2000 if
|
182
|
+
when :wkb12
|
183
|
+
type_code += 1000 if rval.z?
|
184
|
+
type_code += 2000 if rval.m?
|
172
185
|
end
|
173
|
-
emit_integer(type_code)
|
174
|
-
emit_integer(obj.srid) if emit_srid
|
186
|
+
emit_integer(type_code, rval)
|
187
|
+
emit_integer(obj.srid, rval) if emit_srid
|
188
|
+
type_is_collection = [
|
189
|
+
Feature::GeometryCollection,
|
190
|
+
Feature::MultiPoint,
|
191
|
+
Feature::MultiLineString,
|
192
|
+
Feature::MultiPolygon
|
193
|
+
].include?(type)
|
175
194
|
if type == Feature::Point
|
176
|
-
emit_doubles(point_coords(obj))
|
195
|
+
emit_doubles(point_coords(obj, rval), rval)
|
177
196
|
elsif type.subtype_of?(Feature::LineString)
|
178
|
-
emit_line_string_coords(obj)
|
197
|
+
emit_line_string_coords(obj, rval)
|
179
198
|
elsif type == Feature::Polygon
|
180
199
|
exterior_ring = obj.exterior_ring
|
181
200
|
if exterior_ring.empty?
|
182
|
-
emit_integer(0)
|
201
|
+
emit_integer(0, rval)
|
183
202
|
else
|
184
|
-
emit_integer(1 + obj.num_interior_rings)
|
185
|
-
emit_line_string_coords(exterior_ring)
|
186
|
-
obj.interior_rings.each { |r| emit_line_string_coords(r) }
|
203
|
+
emit_integer(1 + obj.num_interior_rings, rval)
|
204
|
+
emit_line_string_coords(exterior_ring, rval)
|
205
|
+
obj.interior_rings.each { |r| emit_line_string_coords(r, rval) }
|
187
206
|
end
|
188
|
-
elsif
|
189
|
-
emit_integer(obj.num_geometries)
|
190
|
-
obj.each { |g| generate_feature(g) }
|
191
|
-
elsif type == Feature::MultiPoint
|
192
|
-
emit_integer(obj.num_geometries)
|
193
|
-
obj.each { |g| generate_feature(g) }
|
194
|
-
elsif type == Feature::MultiLineString
|
195
|
-
emit_integer(obj.num_geometries)
|
196
|
-
obj.each { |g| generate_feature(g) }
|
197
|
-
elsif type == Feature::MultiPolygon
|
198
|
-
emit_integer(obj.num_geometries)
|
199
|
-
obj.each { |g| generate_feature(g) }
|
207
|
+
elsif type_is_collection
|
208
|
+
emit_integer(obj.num_geometries, rval)
|
209
|
+
obj.each { |g| generate_feature(g, rval) }
|
200
210
|
end
|
201
211
|
end
|
202
212
|
end
|
@@ -42,7 +42,6 @@ module RGeo
|
|
42
42
|
# [<tt>:default_srid</tt>]
|
43
43
|
# A SRID to pass to the factory generator if no SRID is present in
|
44
44
|
# the input. Defaults to nil (i.e. don't specify a SRID).
|
45
|
-
|
46
45
|
class WKBParser
|
47
46
|
# Create and configure a WKB parser. See the WKBParser
|
48
47
|
# documentation for the options that can be passed.
|
@@ -62,6 +61,7 @@ module RGeo
|
|
62
61
|
@support_wkb12 = opts[:support_wkb12] ? true : false
|
63
62
|
@ignore_extra_bytes = opts[:ignore_extra_bytes] ? true : false
|
64
63
|
@default_srid = opts[:default_srid]
|
64
|
+
@mutex = Mutex.new
|
65
65
|
end
|
66
66
|
|
67
67
|
# Returns the factory generator. See WKBParser for details.
|
@@ -105,32 +105,32 @@ module RGeo
|
|
105
105
|
# reasons but deprecated. Use #parse instead.
|
106
106
|
|
107
107
|
def parse(data)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
raise Error::ParseError, "Found #{bytes} extra bytes at the end of the stream."
|
108
|
+
@mutex.synchronize do
|
109
|
+
data = [data].pack("H*") if data[0, 1] =~ /[0-9a-fA-F]/
|
110
|
+
@cur_has_z = nil
|
111
|
+
@cur_has_m = nil
|
112
|
+
@cur_srid = nil
|
113
|
+
@cur_dims = 2
|
114
|
+
@cur_factory = nil
|
115
|
+
begin
|
116
|
+
start_scanner(data)
|
117
|
+
obj = parse_object(false)
|
118
|
+
unless @ignore_extra_bytes
|
119
|
+
bytes = bytes_remaining
|
120
|
+
raise Error::ParseError, "Found #{bytes} extra bytes at the end of the stream." if bytes > 0
|
121
121
|
end
|
122
|
+
ensure
|
123
|
+
@data = nil
|
122
124
|
end
|
123
|
-
|
124
|
-
@data = nil
|
125
|
+
obj
|
125
126
|
end
|
126
|
-
obj
|
127
127
|
end
|
128
128
|
alias parse_hex parse
|
129
129
|
|
130
130
|
private
|
131
131
|
|
132
132
|
def parse_object(contained)
|
133
|
-
endian_value =
|
133
|
+
endian_value = byte
|
134
134
|
case endian_value
|
135
135
|
when 0
|
136
136
|
little_endian = false
|
@@ -158,14 +158,20 @@ module RGeo
|
|
158
158
|
if contained != true && contained != type_code
|
159
159
|
raise Error::ParseError, "Enclosed type=#{type_code} is different from container constraint #{contained}"
|
160
160
|
end
|
161
|
+
|
161
162
|
if has_z != @cur_has_z
|
162
163
|
raise Error::ParseError, "Enclosed hasZ=#{has_z} is different from toplevel hasZ=#{@cur_has_z}"
|
163
164
|
end
|
165
|
+
|
164
166
|
if has_m != @cur_has_m
|
165
167
|
raise Error::ParseError, "Enclosed hasM=#{has_m} is different from toplevel hasM=#{@cur_has_m}"
|
166
168
|
end
|
169
|
+
|
167
170
|
if srid && srid != @cur_srid
|
168
|
-
raise
|
171
|
+
raise(
|
172
|
+
Error::ParseError,
|
173
|
+
"Enclosed SRID #{srid} is different from toplevel srid #{@cur_srid || '(unspecified)'}"
|
174
|
+
)
|
169
175
|
end
|
170
176
|
else
|
171
177
|
@cur_has_z = has_z
|
@@ -173,9 +179,11 @@ module RGeo
|
|
173
179
|
@cur_dims = 2 + (@cur_has_z ? 1 : 0) + (@cur_has_m ? 1 : 0)
|
174
180
|
@cur_srid = srid
|
175
181
|
@cur_factory = @factory_generator.call(srid: @cur_srid, has_z_coordinate: has_z, has_m_coordinate: has_m)
|
182
|
+
|
176
183
|
if @cur_has_z && !@cur_factory.property(:has_z_coordinate)
|
177
184
|
raise Error::ParseError, "Data has Z coordinates but the factory doesn't have Z coordinates"
|
178
185
|
end
|
186
|
+
|
179
187
|
if @cur_has_m && !@cur_factory.property(:has_m_coordinate)
|
180
188
|
raise Error::ParseError, "Data has M coordinates but the factory doesn't have M coordinates"
|
181
189
|
end
|
@@ -219,29 +227,23 @@ module RGeo
|
|
219
227
|
@len - @pos
|
220
228
|
end
|
221
229
|
|
222
|
-
def
|
223
|
-
if @pos + 1 > @len
|
224
|
-
raise Error::ParseError, "Not enough bytes left to fulfill 1 byte"
|
225
|
-
end
|
230
|
+
def byte
|
231
|
+
raise Error::ParseError, "Not enough bytes left to fulfill 1 byte" if @pos + 1 > @len
|
226
232
|
str = @data[@pos, 1]
|
227
233
|
@pos += 1
|
228
|
-
str.
|
234
|
+
str.unpack1("C")
|
229
235
|
end
|
230
236
|
|
231
237
|
def get_integer(little_endian)
|
232
|
-
if @pos + 4 > @len
|
233
|
-
raise Error::ParseError, "Not enough bytes left to fulfill 1 integer"
|
234
|
-
end
|
238
|
+
raise Error::ParseError, "Not enough bytes left to fulfill 1 integer" if @pos + 4 > @len
|
235
239
|
str = @data[@pos, 4]
|
236
240
|
@pos += 4
|
237
|
-
str.
|
241
|
+
str.unpack1(little_endian ? "V" : "N")
|
238
242
|
end
|
239
243
|
|
240
244
|
def get_doubles(little_endian, count)
|
241
245
|
len = 8 * count
|
242
|
-
if @pos + len > @len
|
243
|
-
raise Error::ParseError, "Not enough bytes left to fulfill #{count} doubles"
|
244
|
-
end
|
246
|
+
raise Error::ParseError, "Not enough bytes left to fulfill #{count} doubles" if @pos + len > @len
|
245
247
|
str = @data[@pos, len]
|
246
248
|
@pos += len
|
247
249
|
str.unpack("#{little_endian ? 'E' : 'G'}*")
|
@@ -45,15 +45,16 @@ module RGeo
|
|
45
45
|
# letters to lower case; or nil, indicating no case changes from
|
46
46
|
# the default (which is not specified exactly, but is chosen by the
|
47
47
|
# generator to emphasize readability.) Default is nil.
|
48
|
-
|
49
48
|
class WKTGenerator
|
50
49
|
# Create and configure a WKT generator. See the WKTGenerator
|
51
50
|
# documentation for the options that can be passed.
|
52
51
|
|
53
52
|
def initialize(opts = {})
|
54
53
|
@tag_format = opts[:tag_format] || opts[:type_format] || :wkt11
|
55
|
-
@emit_ewkt_srid =
|
56
|
-
|
54
|
+
@emit_ewkt_srid =
|
55
|
+
if @tag_format == :ewkt
|
56
|
+
(opts[:emit_ewkt_srid] ? true : false)
|
57
|
+
end
|
57
58
|
@square_brackets = opts[:square_brackets] ? true : false
|
58
59
|
@convert_case = opts[:convert_case]
|
59
60
|
end
|
@@ -93,16 +94,17 @@ module RGeo
|
|
93
94
|
@end_bracket = @square_brackets ? "]" : ")"
|
94
95
|
factory = obj.factory
|
95
96
|
if @tag_format == :wkt11_strict
|
96
|
-
|
97
|
-
|
97
|
+
support_z = false
|
98
|
+
support_m = false
|
98
99
|
else
|
99
|
-
|
100
|
-
|
100
|
+
support_z = factory.property(:has_z_coordinate)
|
101
|
+
support_m = factory.property(:has_m_coordinate)
|
101
102
|
end
|
102
|
-
str = generate_feature(obj, true)
|
103
|
-
|
103
|
+
str = generate_feature(obj, support_z, support_m, toplevel: true)
|
104
|
+
case @convert_case
|
105
|
+
when :upper
|
104
106
|
str.upcase
|
105
|
-
|
107
|
+
when :lower
|
106
108
|
str.downcase
|
107
109
|
else
|
108
110
|
str
|
@@ -111,99 +113,104 @@ module RGeo
|
|
111
113
|
|
112
114
|
private
|
113
115
|
|
114
|
-
def generate_feature(obj, toplevel
|
116
|
+
def generate_feature(obj, support_z, support_m, toplevel: false)
|
115
117
|
type = obj.geometry_type
|
116
118
|
type = Feature::LineString if type.subtype_of?(Feature::LineString)
|
117
119
|
tag = type.type_name.dup
|
118
|
-
|
119
|
-
|
120
|
+
case @tag_format
|
121
|
+
when :ewkt
|
122
|
+
tag << "M" if support_m && !support_z
|
120
123
|
tag = "SRID=#{obj.srid};#{tag}" if toplevel && @emit_ewkt_srid
|
121
|
-
|
122
|
-
if
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
124
|
+
when :wkt12
|
125
|
+
if support_z
|
126
|
+
tag <<
|
127
|
+
if support_m
|
128
|
+
" ZM"
|
129
|
+
else
|
130
|
+
" Z"
|
131
|
+
end
|
132
|
+
elsif support_m
|
129
133
|
tag << " M"
|
130
134
|
end
|
131
135
|
end
|
132
136
|
if type == Feature::Point
|
133
|
-
"#{tag} #{generate_point(obj)}"
|
137
|
+
"#{tag} #{generate_point(obj, support_z, support_m)}"
|
134
138
|
elsif type == Feature::LineString
|
135
|
-
"#{tag} #{generate_line_string(obj)}"
|
139
|
+
"#{tag} #{generate_line_string(obj, support_z, support_m)}"
|
136
140
|
elsif type == Feature::Polygon
|
137
|
-
"#{tag} #{generate_polygon(obj)}"
|
141
|
+
"#{tag} #{generate_polygon(obj, support_z, support_m)}"
|
138
142
|
elsif type == Feature::GeometryCollection
|
139
|
-
"#{tag} #{generate_geometry_collection(obj)}"
|
143
|
+
"#{tag} #{generate_geometry_collection(obj, support_z, support_m)}"
|
140
144
|
elsif type == Feature::MultiPoint
|
141
|
-
"#{tag} #{generate_multi_point(obj)}"
|
145
|
+
"#{tag} #{generate_multi_point(obj, support_z, support_m)}"
|
142
146
|
elsif type == Feature::MultiLineString
|
143
|
-
"#{tag} #{generate_multi_line_string(obj)}"
|
147
|
+
"#{tag} #{generate_multi_line_string(obj, support_z, support_m)}"
|
144
148
|
elsif type == Feature::MultiPolygon
|
145
|
-
"#{tag} #{generate_multi_polygon(obj)}"
|
149
|
+
"#{tag} #{generate_multi_polygon(obj, support_z, support_m)}"
|
146
150
|
else
|
147
151
|
raise Error::ParseError, "Unrecognized geometry type: #{type}"
|
148
152
|
end
|
149
153
|
end
|
150
154
|
|
151
|
-
def generate_coords(obj)
|
155
|
+
def generate_coords(obj, support_z, support_m)
|
152
156
|
str = +"#{obj.x} #{obj.y}"
|
153
|
-
str << " #{obj.z}" if
|
154
|
-
str << " #{obj.m}" if
|
157
|
+
str << " #{obj.z}" if support_z
|
158
|
+
str << " #{obj.m}" if support_m
|
155
159
|
str
|
156
160
|
end
|
157
161
|
|
158
|
-
def generate_point(obj)
|
159
|
-
"#{@begin_bracket}#{generate_coords(obj)}#{@end_bracket}"
|
162
|
+
def generate_point(obj, support_z, support_m)
|
163
|
+
"#{@begin_bracket}#{generate_coords(obj, support_z, support_m)}#{@end_bracket}"
|
160
164
|
end
|
161
165
|
|
162
|
-
def generate_line_string(obj)
|
166
|
+
def generate_line_string(obj, support_z, support_m)
|
163
167
|
if obj.empty?
|
164
168
|
"EMPTY"
|
165
169
|
else
|
166
|
-
|
170
|
+
points = obj.points.map { |p| generate_coords(p, support_z, support_m) }
|
171
|
+
"#{@begin_bracket}#{points.join(', ')}#{@end_bracket}"
|
167
172
|
end
|
168
173
|
end
|
169
174
|
|
170
|
-
def generate_polygon(obj)
|
175
|
+
def generate_polygon(obj, support_z, support_m)
|
171
176
|
if obj.empty?
|
172
177
|
"EMPTY"
|
173
178
|
else
|
174
|
-
|
179
|
+
lines = [generate_line_string(obj.exterior_ring, support_z, support_m)]
|
180
|
+
lines += obj.interior_rings.map { |r| generate_line_string(r, support_z, support_m) }
|
181
|
+
"#{@begin_bracket}#{lines.join(', ')}#{@end_bracket}"
|
175
182
|
end
|
176
183
|
end
|
177
184
|
|
178
|
-
def generate_geometry_collection(obj)
|
185
|
+
def generate_geometry_collection(obj, support_z, support_m)
|
179
186
|
if obj.empty?
|
180
187
|
"EMPTY"
|
181
188
|
else
|
182
|
-
"#{@begin_bracket}#{obj.map { |f| generate_feature(f) }.join(', ')}#{@end_bracket}"
|
189
|
+
"#{@begin_bracket}#{obj.map { |f| generate_feature(f, support_z, support_m) }.join(', ')}#{@end_bracket}"
|
183
190
|
end
|
184
191
|
end
|
185
192
|
|
186
|
-
def generate_multi_point(obj)
|
193
|
+
def generate_multi_point(obj, support_z, support_m)
|
187
194
|
if obj.empty?
|
188
195
|
"EMPTY"
|
189
196
|
else
|
190
|
-
"#{@begin_bracket}#{obj.map { |f| generate_point(f) }.join(', ')}#{@end_bracket}"
|
197
|
+
"#{@begin_bracket}#{obj.map { |f| generate_point(f, support_z, support_m) }.join(', ')}#{@end_bracket}"
|
191
198
|
end
|
192
199
|
end
|
193
200
|
|
194
|
-
def generate_multi_line_string(obj)
|
201
|
+
def generate_multi_line_string(obj, support_z, support_m)
|
195
202
|
if obj.empty?
|
196
203
|
"EMPTY"
|
197
204
|
else
|
198
|
-
"#{@begin_bracket}#{obj.map { |f| generate_line_string(f) }.join(', ')}#{@end_bracket}"
|
205
|
+
"#{@begin_bracket}#{obj.map { |f| generate_line_string(f, support_z, support_m) }.join(', ')}#{@end_bracket}"
|
199
206
|
end
|
200
207
|
end
|
201
208
|
|
202
|
-
def generate_multi_polygon(obj)
|
209
|
+
def generate_multi_polygon(obj, support_z, support_m)
|
203
210
|
if obj.empty?
|
204
211
|
"EMPTY"
|
205
212
|
else
|
206
|
-
"#{@begin_bracket}#{obj.map { |f| generate_polygon(f) }.join(', ')}#{@end_bracket}"
|
213
|
+
"#{@begin_bracket}#{obj.map { |f| generate_polygon(f, support_z, support_m) }.join(', ')}#{@end_bracket}"
|
207
214
|
end
|
208
215
|
end
|
209
216
|
end
|