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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/README.md +23 -14
  4. data/ext/geos_c_impl/analysis.c +30 -25
  5. data/ext/geos_c_impl/analysis.h +8 -7
  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 +42 -28
  11. data/ext/geos_c_impl/factory.c +540 -451
  12. data/ext/geos_c_impl/factory.h +105 -95
  13. data/ext/geos_c_impl/geometry.c +593 -387
  14. data/ext/geos_c_impl/geometry.h +10 -5
  15. data/ext/geos_c_impl/geometry_collection.c +306 -339
  16. data/ext/geos_c_impl/geometry_collection.h +6 -20
  17. data/ext/geos_c_impl/globals.c +169 -0
  18. data/ext/geos_c_impl/globals.h +46 -0
  19. data/ext/geos_c_impl/line_string.c +271 -231
  20. data/ext/geos_c_impl/line_string.h +5 -8
  21. data/ext/geos_c_impl/main.c +16 -16
  22. data/ext/geos_c_impl/point.c +65 -67
  23. data/ext/geos_c_impl/point.h +4 -7
  24. data/ext/geos_c_impl/polygon.c +137 -135
  25. data/ext/geos_c_impl/polygon.h +11 -11
  26. data/ext/geos_c_impl/preface.h +16 -10
  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 +303 -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 +69 -85
  58. data/lib/rgeo/geographic/factory.rb +98 -125
  59. data/lib/rgeo/geographic/interface.rb +69 -166
  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 +26 -25
  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 +105 -173
  72. data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
  73. data/lib/rgeo/geos/ffi_feature_methods.rb +105 -127
  74. data/lib/rgeo/geos/interface.rb +20 -59
  75. data/lib/rgeo/geos/utils.rb +5 -5
  76. data/lib/rgeo/geos/zm_factory.rb +53 -95
  77. data/lib/rgeo/geos/zm_feature_methods.rb +30 -33
  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 +50 -13
  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.3.1"
4
+ VERSION = "3.0.1"
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