rgeo 2.4.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/README.md +21 -11
  4. data/ext/geos_c_impl/analysis.c +29 -26
  5. data/ext/geos_c_impl/analysis.h +8 -5
  6. data/ext/geos_c_impl/coordinates.c +27 -21
  7. data/ext/geos_c_impl/coordinates.h +5 -2
  8. data/ext/geos_c_impl/errors.c +19 -10
  9. data/ext/geos_c_impl/errors.h +11 -4
  10. data/ext/geos_c_impl/extconf.rb +41 -29
  11. data/ext/geos_c_impl/factory.c +441 -351
  12. data/ext/geos_c_impl/factory.h +98 -55
  13. data/ext/geos_c_impl/geometry.c +563 -384
  14. data/ext/geos_c_impl/geometry.h +10 -3
  15. data/ext/geos_c_impl/geometry_collection.c +288 -316
  16. data/ext/geos_c_impl/geometry_collection.h +6 -18
  17. data/ext/geos_c_impl/globals.c +99 -21
  18. data/ext/geos_c_impl/globals.h +3 -2
  19. data/ext/geos_c_impl/line_string.c +263 -222
  20. data/ext/geos_c_impl/line_string.h +5 -6
  21. data/ext/geos_c_impl/main.c +8 -9
  22. data/ext/geos_c_impl/point.c +62 -65
  23. data/ext/geos_c_impl/point.h +4 -5
  24. data/ext/geos_c_impl/polygon.c +134 -132
  25. data/ext/geos_c_impl/polygon.h +11 -9
  26. data/ext/geos_c_impl/preface.h +10 -12
  27. data/ext/geos_c_impl/ruby_more.c +67 -0
  28. data/ext/geos_c_impl/ruby_more.h +25 -0
  29. data/lib/rgeo/cartesian/analysis.rb +5 -3
  30. data/lib/rgeo/cartesian/bounding_box.rb +74 -79
  31. data/lib/rgeo/cartesian/calculations.rb +64 -33
  32. data/lib/rgeo/cartesian/factory.rb +57 -102
  33. data/lib/rgeo/cartesian/feature_classes.rb +68 -46
  34. data/lib/rgeo/cartesian/feature_methods.rb +67 -25
  35. data/lib/rgeo/cartesian/interface.rb +6 -41
  36. data/lib/rgeo/cartesian/planar_graph.rb +373 -0
  37. data/lib/rgeo/cartesian/sweepline_intersector.rb +147 -0
  38. data/lib/rgeo/cartesian/valid_op.rb +69 -0
  39. data/lib/rgeo/cartesian.rb +3 -0
  40. data/lib/rgeo/coord_sys/cs/entities.rb +299 -99
  41. data/lib/rgeo/coord_sys/cs/factories.rb +0 -2
  42. data/lib/rgeo/coord_sys/cs/wkt_parser.rb +90 -42
  43. data/lib/rgeo/coord_sys.rb +1 -20
  44. data/lib/rgeo/error.rb +15 -0
  45. data/lib/rgeo/feature/curve.rb +0 -11
  46. data/lib/rgeo/feature/factory.rb +26 -36
  47. data/lib/rgeo/feature/factory_generator.rb +6 -14
  48. data/lib/rgeo/feature/geometry.rb +146 -66
  49. data/lib/rgeo/feature/geometry_collection.rb +16 -9
  50. data/lib/rgeo/feature/line_string.rb +4 -5
  51. data/lib/rgeo/feature/linear_ring.rb +0 -1
  52. data/lib/rgeo/feature/multi_curve.rb +0 -6
  53. data/lib/rgeo/feature/multi_surface.rb +3 -4
  54. data/lib/rgeo/feature/point.rb +4 -5
  55. data/lib/rgeo/feature/polygon.rb +1 -2
  56. data/lib/rgeo/feature/surface.rb +3 -4
  57. data/lib/rgeo/feature/types.rb +73 -83
  58. data/lib/rgeo/geographic/factory.rb +98 -125
  59. data/lib/rgeo/geographic/interface.rb +66 -163
  60. data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
  61. data/lib/rgeo/geographic/projected_feature_methods.rb +67 -42
  62. data/lib/rgeo/geographic/projected_window.rb +36 -22
  63. data/lib/rgeo/geographic/{proj4_projector.rb → projector.rb} +3 -5
  64. data/lib/rgeo/geographic/simple_mercator_projector.rb +24 -23
  65. data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
  66. data/lib/rgeo/geographic/spherical_feature_methods.rb +86 -9
  67. data/lib/rgeo/geographic/spherical_math.rb +17 -20
  68. data/lib/rgeo/geographic.rb +1 -1
  69. data/lib/rgeo/geos/capi_factory.rb +87 -158
  70. data/lib/rgeo/geos/capi_feature_classes.rb +50 -36
  71. data/lib/rgeo/geos/ffi_factory.rb +95 -165
  72. data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
  73. data/lib/rgeo/geos/ffi_feature_methods.rb +105 -126
  74. data/lib/rgeo/geos/interface.rb +20 -59
  75. data/lib/rgeo/geos/utils.rb +3 -3
  76. data/lib/rgeo/geos/zm_factory.rb +53 -95
  77. data/lib/rgeo/geos/zm_feature_methods.rb +30 -32
  78. data/lib/rgeo/geos.rb +8 -8
  79. data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +9 -22
  80. data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -2
  81. data/lib/rgeo/impl_helper/basic_line_string_methods.rb +28 -56
  82. data/lib/rgeo/impl_helper/basic_point_methods.rb +2 -14
  83. data/lib/rgeo/impl_helper/basic_polygon_methods.rb +17 -26
  84. data/lib/rgeo/impl_helper/utils.rb +21 -0
  85. data/lib/rgeo/impl_helper/valid_op.rb +350 -0
  86. data/lib/rgeo/impl_helper/validity_check.rb +139 -0
  87. data/lib/rgeo/impl_helper.rb +1 -0
  88. data/lib/rgeo/version.rb +1 -1
  89. data/lib/rgeo/wkrep/wkb_generator.rb +73 -63
  90. data/lib/rgeo/wkrep/wkb_parser.rb +33 -31
  91. data/lib/rgeo/wkrep/wkt_generator.rb +52 -45
  92. data/lib/rgeo/wkrep/wkt_parser.rb +48 -35
  93. data/lib/rgeo.rb +1 -3
  94. metadata +51 -16
  95. data/lib/rgeo/coord_sys/srs_database/entry.rb +0 -107
  96. data/lib/rgeo/coord_sys/srs_database/sr_org.rb +0 -64
  97. 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
@@ -7,3 +7,4 @@ require_relative "impl_helper/basic_geometry_collection_methods"
7
7
  require_relative "impl_helper/basic_point_methods"
8
8
  require_relative "impl_helper/basic_line_string_methods"
9
9
  require_relative "impl_helper/basic_polygon_methods"
10
+ require_relative "impl_helper/valid_op"
data/lib/rgeo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RGeo
4
- VERSION = "2.4.0"
4
+ VERSION = "3.0.0"
5
5
  end
@@ -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
- @cur_has_z = factory.property(:has_z_coordinate)
104
- @cur_has_m = factory.property(:has_m_coordinate)
102
+ has_z = factory.property(:has_z_coordinate)
103
+ has_m = factory.property(:has_m_coordinate)
105
104
  else
106
- @cur_has_z = nil
107
- @cur_has_m = nil
105
+ has_z = false
106
+ has_m = false
108
107
  end
109
- @cur_dims = 2 + (@cur_has_z ? 1 : 0) + (@cur_has_m ? 1 : 0)
110
- start_emitter
111
- generate_feature(obj, true)
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
- def emit_byte(value)
118
- @cur_array << [value].pack("C")
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
- @cur_array << [value].pack(@little_endian ? "V" : "N")
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
- @cur_array << array.pack(@little_endian ? "E*" : "G*")
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 { |p| point_coords(p, array) }
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 @cur_has_z
140
- array << obj.m if @cur_has_m
163
+ array << obj.z if rval.z?
164
+ array << obj.m if rval.m?
141
165
  array
142
166
  end
143
167
 
144
- def start_emitter
145
- @cur_array = []
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
- if @type_format == :ewkb
163
- type_code |= 0x80000000 if @cur_has_z
164
- type_code |= 0x40000000 if @cur_has_m
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
- elsif @type_format == :wkb12
170
- type_code += 1000 if @cur_has_z
171
- type_code += 2000 if @cur_has_m
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 type == Feature::GeometryCollection
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
- data = [data].pack("H*") if data[0, 1] =~ /[0-9a-fA-F]/
109
- @cur_has_z = nil
110
- @cur_has_m = nil
111
- @cur_srid = nil
112
- @cur_dims = 2
113
- @cur_factory = nil
114
- begin
115
- start_scanner(data)
116
- obj = parse_object(false)
117
- unless @ignore_extra_bytes
118
- bytes = bytes_remaining
119
- if bytes > 0
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
- ensure
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 = get_byte
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 Error::ParseError, "Enclosed SRID #{srid} is different from toplevel srid #{@cur_srid || '(unspecified)'}"
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 get_byte
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.unpack("C").first
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.unpack(little_endian ? "V" : "N").first
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 = @tag_format == :ewkt ?
56
- (opts[:emit_ewkt_srid] ? true : false) : nil
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
- @cur_support_z = nil
97
- @cur_support_m = nil
97
+ support_z = false
98
+ support_m = false
98
99
  else
99
- @cur_support_z = factory.property(:has_z_coordinate)
100
- @cur_support_m = factory.property(:has_m_coordinate)
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
- if @convert_case == :upper
103
+ str = generate_feature(obj, support_z, support_m, toplevel: true)
104
+ case @convert_case
105
+ when :upper
104
106
  str.upcase
105
- elsif @convert_case == :lower
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 = false)
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
- if @tag_format == :ewkt
119
- tag << "M" if @cur_support_m && !@cur_support_z
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
- elsif @tag_format == :wkt12
122
- if @cur_support_z
123
- if @cur_support_m
124
- tag << " ZM"
125
- else
126
- tag << " Z"
127
- end
128
- elsif @cur_support_m
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 @cur_support_z
154
- str << " #{obj.m}" if @cur_support_m
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
- "#{@begin_bracket}#{obj.points.map { |p| generate_coords(p) }.join(', ')}#{@end_bracket}"
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
- "#{@begin_bracket}#{([generate_line_string(obj.exterior_ring)] + obj.interior_rings.map { |r| generate_line_string(r) }).join(', ')}#{@end_bracket}"
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