rgeo 0.1.13 → 0.1.14

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 (41) hide show
  1. data/History.rdoc +11 -0
  2. data/Version +1 -1
  3. data/ext/geos_c_impl/factory.c +35 -40
  4. data/ext/geos_c_impl/factory.h +4 -1
  5. data/ext/geos_c_impl/geometry_collection.c +5 -5
  6. data/ext/geos_c_impl/geometry_collection.h +1 -1
  7. data/ext/geos_c_impl/line_string.c +129 -116
  8. data/ext/geos_c_impl/point.c +22 -33
  9. data/ext/geos_c_impl/point.h +1 -6
  10. data/ext/geos_c_impl/polygon.c +4 -4
  11. data/ext/geos_c_impl/polygon.h +1 -1
  12. data/lib/rgeo.rb +1 -0
  13. data/lib/rgeo/cartesian/simple_factory.rb +20 -4
  14. data/lib/rgeo/errors.rb +8 -0
  15. data/lib/rgeo/features/factory.rb +35 -1
  16. data/lib/rgeo/features/point.rb +22 -0
  17. data/lib/rgeo/features/polygon.rb +1 -2
  18. data/lib/rgeo/features/types.rb +70 -16
  19. data/lib/rgeo/geography/factories.rb +40 -3
  20. data/lib/rgeo/geography/factory.rb +25 -5
  21. data/lib/rgeo/geography/simple_mercator/feature_methods.rb +2 -6
  22. data/lib/rgeo/geography/simple_mercator/projector.rb +1 -1
  23. data/lib/rgeo/geos/factory.rb +40 -46
  24. data/lib/rgeo/geos/interface.rb +10 -0
  25. data/lib/rgeo/impl_helpers.rb +0 -1
  26. data/lib/rgeo/impl_helpers/basic_geometry_methods.rb +2 -2
  27. data/lib/rgeo/impl_helpers/basic_point_methods.rb +12 -14
  28. data/lib/rgeo/wkrep.rb +59 -0
  29. data/lib/rgeo/wkrep/wkb_generator.rb +181 -0
  30. data/lib/rgeo/wkrep/wkb_parser.rb +205 -0
  31. data/lib/rgeo/wkrep/wkt_generator.rb +187 -0
  32. data/lib/rgeo/wkrep/wkt_parser.rb +321 -0
  33. data/tests/common/line_string_tests.rb +26 -2
  34. data/tests/common/point_tests.rb +26 -0
  35. data/tests/geos/tc_point.rb +1 -20
  36. data/tests/simple_cartesian/tc_point.rb +1 -0
  37. data/tests/simple_mercator/tc_point.rb +1 -0
  38. data/tests/simple_spherical/tc_point.rb +1 -0
  39. data/tests/tc_oneoff.rb +3 -2
  40. metadata +8 -4
  41. data/lib/rgeo/impl_helpers/serialization.rb +0 -130
@@ -0,0 +1,205 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Well-known binary parser for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module RGeo
38
+
39
+ module WKRep
40
+
41
+
42
+ class WKBParser
43
+
44
+
45
+ def initialize(factory_, opts_={}, &block_)
46
+ @factory = factory_
47
+ @factory_factory = block_
48
+ @support_ewkb = opts_[:support_ewkb]
49
+ @support_wkb12 = opts_[:support_wkb12]
50
+ @ignore_extra_bytes = opts_[:ignore_extra_bytes]
51
+ end
52
+
53
+
54
+ def parse(data_)
55
+ @cur_has_z = nil
56
+ @cur_has_m = nil
57
+ @cur_srid = nil
58
+ @cur_dims = 2
59
+ @cur_factory = @factory
60
+ begin
61
+ _start_scanner(data_)
62
+ obj_ = _parse_object(false)
63
+ unless @ignore_extra_bytes
64
+ bytes_ = _bytes_remaining
65
+ if bytes_ > 0
66
+ raise Errors::ParseError, "Found #{bytes_} extra bytes at the end of the stream."
67
+ end
68
+ end
69
+ ensure
70
+ _clean_scanner
71
+ end
72
+ obj_
73
+ end
74
+
75
+
76
+ def _parse_object(contained_) # :nodoc:
77
+ little_endian_ = _get_byte == 1
78
+ type_code_ = _get_integer(little_endian_)
79
+ has_z_ = false
80
+ has_m_ = false
81
+ srid_ = nil
82
+ if @support_ewkb
83
+ has_z_ ||= type_code_ & 0x80000000 != 0
84
+ has_m_ ||= type_code_ & 0x40000000 != 0
85
+ srid_ = _get_integer(little_endian_) if type_code_ & 0x20000000 != 0
86
+ type_code_ &= 0x0fffffff
87
+ end
88
+ if @support_wkb12
89
+ has_z_ ||= (type_code_ / 1000) & 1 != 0
90
+ has_m_ ||= (type_code_ / 1000) & 2 != 0
91
+ type_code_ %= 1000
92
+ end
93
+ if contained_
94
+ if contained_ != true && contained_ != type_code_
95
+ raise Errors::ParseError, "Enclosed type=#{type_code_} is different from container constraint #{contained_}"
96
+ end
97
+ if has_z_ != @cur_has_z
98
+ raise Errors::ParseError, "Enclosed hasZ=#{has_z_} is different from toplevel hasZ=#{@cur_has_z}"
99
+ end
100
+ if has_m_ != @cur_has_m
101
+ raise Errors::ParseError, "Enclosed hasM=#{has_m_} is different from toplevel hasM=#{@cur_has_m}"
102
+ end
103
+ if srid_ && srid_ != @cur_srid
104
+ raise Errors::ParseError, "Enclosed SRID #{srid_} is different from toplevel srid #{@cur_srid || '(unspecified)'}"
105
+ end
106
+ else
107
+ @cur_has_z = has_z_
108
+ @cur_has_m = has_m_
109
+ @cur_dims = 2 + (@cur_has_z ? 1 : 0) + (@cur_has_m ? 1 : 0)
110
+ @cur_srid = srid_
111
+ if srid_ && @factory_factory
112
+ @cur_factory = @factory_factory.call(srid_)
113
+ end
114
+ if @cur_has_z && !@cur_factory.has_capability?(:z_coordinate)
115
+ raise Errors::ParseError, "Data has Z coordinates but the factory doesn't have z_coordinate capability"
116
+ end
117
+ if @cur_has_m && !@cur_factory.has_capability?(:m_coordinate)
118
+ raise Errors::ParseError, "Data has M coordinates but the factory doesn't have m_coordinate capability"
119
+ end
120
+ end
121
+ case type_code_
122
+ when 1
123
+ coords_ = _get_doubles(little_endian_, @cur_dims)
124
+ @cur_factory.point(*coords_)
125
+ when 2
126
+ _parse_line_string(little_endian_)
127
+ when 3
128
+ interior_rings_ = (1.._get_integer(little_endian_)).map{ _parse_line_string(little_endian_) }
129
+ exterior_ring_ = interior_rings_.shift || @cur_factory.linear_ring([])
130
+ @cur_factory.polygon(exterior_ring_, interior_rings_)
131
+ when 4
132
+ @cur_factory.multi_point((1.._get_integer(little_endian_)).map{ _parse_object(1) })
133
+ when 5
134
+ @cur_factory.multi_line_string((1.._get_integer(little_endian_)).map{ _parse_object(2) })
135
+ when 6
136
+ @cur_factory.multi_polygon((1.._get_integer(little_endian_)).map{ _parse_object(3) })
137
+ when 7
138
+ @cur_factory.collection((1.._get_integer(little_endian_)).map{ _parse_object(true) })
139
+ else
140
+ raise Errors::ParseError, "Unknown type value: #{type_code_}."
141
+ end
142
+ end
143
+
144
+
145
+ def _parse_line_string(little_endian_) # :nodoc:
146
+ count_ = _get_integer(little_endian_)
147
+ coords_ = _get_doubles(little_endian_, @cur_dims * count_)
148
+ @cur_factory.line_string((0...count_).map{ |i_| @cur_factory.point(*coords_[@cur_dims*i_,@cur_dims]) })
149
+ end
150
+
151
+
152
+ def _start_scanner(data_) # :nodoc:
153
+ @_data = data_
154
+ @_len = data_.length
155
+ @_pos = 0
156
+ end
157
+
158
+
159
+ def _clean_scanner # :nodoc:
160
+ @_data = nil
161
+ end
162
+
163
+
164
+ def _bytes_remaining # :nodoc:
165
+ @_len - @_pos
166
+ end
167
+
168
+
169
+ def _get_byte # :nodoc:
170
+ if @_pos + 1 > @_len
171
+ raise Errors::ParseError, "Not enough bytes left to fulfill 1 byte"
172
+ end
173
+ str_ = @_data[@_pos, 1]
174
+ @_pos += 1
175
+ str_.unpack("C").first
176
+ end
177
+
178
+
179
+ def _get_integer(little_endian_) # :nodoc:
180
+ if @_pos + 4 > @_len
181
+ raise Errors::ParseError, "Not enough bytes left to fulfill 1 integer"
182
+ end
183
+ str_ = @_data[@_pos, 4]
184
+ @_pos += 4
185
+ str_.unpack("#{little_endian_ ? 'V' : 'N'}").first
186
+ end
187
+
188
+
189
+ def _get_doubles(little_endian_, count_) # :nodoc:
190
+ len_ = 8 * count_
191
+ if @_pos + len_ > @_len
192
+ raise Errors::ParseError, "Not enough bytes left to fulfill #{count_} doubles"
193
+ end
194
+ str_ = @_data[@_pos, len_]
195
+ @_pos += len_
196
+ str_.unpack("#{little_endian_ ? 'E' : 'G'}*")
197
+ end
198
+
199
+
200
+ end
201
+
202
+
203
+ end
204
+
205
+ end
@@ -0,0 +1,187 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Well-known text generator for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module RGeo
38
+
39
+ module WKRep
40
+
41
+
42
+ class WKTGenerator
43
+
44
+
45
+ def initialize(opts_={})
46
+ @tag_format = opts_[:tag_format]
47
+ @emit_ewkt_srid = opts_[:emit_ewkt_srid] if @tag_format == :ewkt
48
+ @begin_bracket = opts_[:square_brackets] ? '[' : '('
49
+ @end_bracket = opts_[:square_brackets] ? ']' : ')'
50
+ @case = opts_[:case]
51
+ end
52
+
53
+
54
+ def generate(obj_)
55
+ factory_ = obj_.factory
56
+ if @tag_format == :wkt11_strict
57
+ @cur_support_z = nil
58
+ @cur_support_m = nil
59
+ else
60
+ @cur_support_z = factory_.has_capability?(:z_coordinate)
61
+ @cur_support_m = factory_.has_capability?(:m_coordinate)
62
+ end
63
+ str_ = _generate_feature(obj_, true)
64
+ if @case == :upper
65
+ str_.upcase
66
+ elsif @case == :lower
67
+ str_.downcase
68
+ else
69
+ str_
70
+ end
71
+ end
72
+
73
+
74
+ def _generate_feature(obj_, toplevel_=false) # :nodoc:
75
+ type_ = obj_.geometry_type
76
+ tag_ = type_.type_name
77
+ if @tag_format == :ewkt
78
+ if @cur_support_m && !@cur_support_z
79
+ tag_ << 'M'
80
+ end
81
+ elsif @tag_format == :wkt12
82
+ if @cur_support_z
83
+ if @cur_support_m
84
+ tag_ << ' ZM'
85
+ else
86
+ tag_ << ' Z'
87
+ end
88
+ elsif @cur_support_m
89
+ tag_ << ' M'
90
+ end
91
+ end
92
+ if toplevel_ && @emit_ewkt_srid
93
+ tag_ = "SRID=#{obj_.srid};#{tag_}"
94
+ end
95
+ if type_ == Features::Point
96
+ tag_ + _generate_point(obj_)
97
+ elsif type_.subtype_of?(Features::LineString)
98
+ tag_ + _generate_line_string(obj_)
99
+ elsif type_ == Features::Polygon
100
+ tag_ + _generate_polygon(obj_)
101
+ elsif type_ == Features::GeometryCollection
102
+ tag_ + _generate_geometry_collection(obj_)
103
+ elsif type_ == Features::MultiPoint
104
+ tag_ + _generate_multi_point(obj_)
105
+ elsif type_ == Features::MultiLineString
106
+ tag_ + _generate_multi_line_string(obj_)
107
+ elsif type_ == Features::MultiPolygon
108
+ tag_ + _generate_multi_polygon(obj_)
109
+ else
110
+ raise Errors::ParseError, "Unrecognized geometry type: #{type_}"
111
+ end
112
+ end
113
+
114
+
115
+ def _generate_coords(obj_) # :nodoc:
116
+ str_ = "#{obj_.x.to_s} #{obj_.y.to_s}"
117
+ str_ << " #{obj_.z.to_s}" if @cur_support_z
118
+ str_ << " #{obj_.m.to_s}" if @cur_support_m
119
+ str_
120
+ end
121
+
122
+
123
+ def _generate_point(obj_) # :nodoc:
124
+ "#{@begin_bracket}#{_generate_coords(obj_)}#{@end_bracket}"
125
+ end
126
+
127
+
128
+ def _generate_line_string(obj_) # :nodoc:
129
+ if obj_.is_empty?
130
+ " EMPTY"
131
+ else
132
+ "#{@begin_bracket}#{obj_.points.map{ |p_| _generate_coords(p_) }.join(',')}#{@end_bracket}"
133
+ end
134
+ end
135
+
136
+
137
+ def _generate_polygon(obj_) # :nodoc:
138
+ if obj_.is_empty?
139
+ " EMPTY"
140
+ else
141
+ "#{@begin_bracket}#{([_generate_line_string(obj_.exterior_ring)] + obj_.interior_rings.map{ |r_| _generate_line_string(r_) }).join(',')}#{@end_bracket}"
142
+ end
143
+ end
144
+
145
+
146
+ def _generate_geometry_collection(obj_) # :nodoc:
147
+ if obj_.is_empty?
148
+ " EMPTY"
149
+ else
150
+ "#{@begin_bracket}#{obj_.map{ |f_| _generate_feature(f_) }.join(',')}#{@end_bracket}"
151
+ end
152
+ end
153
+
154
+
155
+ def _generate_multi_point(obj_) # :nodoc:
156
+ if obj_.is_empty?
157
+ " EMPTY"
158
+ else
159
+ "#{@begin_bracket}#{obj_.map{ |f_| _generate_point(f_) }.join(',')}#{@end_bracket}"
160
+ end
161
+ end
162
+
163
+
164
+ def _generate_multi_line_string(obj_) # :nodoc:
165
+ if obj_.is_empty?
166
+ " EMPTY"
167
+ else
168
+ "#{@begin_bracket}#{obj_.map{ |f_| _generate_line_string(f_) }.join(',')}#{@end_bracket}"
169
+ end
170
+ end
171
+
172
+
173
+ def _generate_multi_polygon(obj_) # :nodoc:
174
+ if obj_.is_empty?
175
+ " EMPTY"
176
+ else
177
+ "#{@begin_bracket}#{obj_.map{ |f_| _generate_polygon(f_) }.join(',')}#{@end_bracket}"
178
+ end
179
+ end
180
+
181
+
182
+ end
183
+
184
+
185
+ end
186
+
187
+ end
@@ -0,0 +1,321 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Well-known text parser for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ require 'strscan'
38
+
39
+
40
+ module RGeo
41
+
42
+ module WKRep
43
+
44
+
45
+ class WKTParser
46
+
47
+
48
+ def initialize(factory_, opts_={}, &block_)
49
+ @factory = factory_
50
+ @factory_factory = block_
51
+ @support_ewkt = opts_[:support_ewkt]
52
+ @support_wkt12 = opts_[:support_wkt12]
53
+ @support_higher_dimensions = opts_[:support_higher_dimensions] || @support_ewkt || @support_wkt12
54
+ @ignore_extra_tokens = opts_[:ignore_extra_tokens]
55
+ end
56
+
57
+
58
+ def parse(str_)
59
+ @cur_factory = @factory
60
+ str_ = str_.downcase
61
+ if @support_ewkt && str_ =~ /^srid=(\d+);/
62
+ str_ = $'
63
+ if @factory_factory
64
+ @cur_factory = @factory_factory.call($1.to_i)
65
+ end
66
+ end
67
+ @cur_factory_support_z = @cur_factory.has_capability?(:z_coordinate)
68
+ @cur_factory_support_m = @cur_factory.has_capability?(:m_coordinate)
69
+ _start_scanner(str_)
70
+ obj_ = _parse_type_tag(nil, nil)
71
+ if @cur_token && !@ignore_extra_tokens
72
+ raise Errors::ParseError, "Extra tokens beginning with #{@cur_token.inspect}."
73
+ end
74
+ obj_
75
+ end
76
+
77
+
78
+ def _parse_type_tag(expect_z_, expect_m_) # :nodoc:
79
+ _expect_token_type(::String)
80
+ if @support_ekwt && @cur_token =~ /^(.+)(z?m?)$/
81
+ type_ = $1
82
+ zm_ = $2
83
+ else
84
+ type_ = @cur_token
85
+ zm_ = ''
86
+ end
87
+ _next_token
88
+ if zm_.length == 0 && @support_wkt12 && @cur_token.kind_of?(::String) && @cur_token =~ /^z?m?$/
89
+ zm_ = @cur_token
90
+ _next_token
91
+ end
92
+ if zm_.length > 0 || !@support_higher_dimensions
93
+ nexpect_z_ = zm_[0,1] == 'z'
94
+ if !expect_z_.nil? && nexpect_z_ != expect_z_
95
+ raise Errors::ParseError, "Surrounding collection has Z but contained geometry doesn't."
96
+ end
97
+ expect_z_ = nexpect_z_
98
+ nexpect_m_ = zm_[-1,1] == 'm'
99
+ if !expect_m_.nil? && nexpect_m_ != expect_m_
100
+ raise Errors::ParseError, "Surrounding collection has M but contained geometry doesn't."
101
+ end
102
+ expect_m_ = nexpect_m_
103
+ end
104
+ if expect_z_ && !@cur_factory_support_z
105
+ raise Errors::ParseError, "Type tag declares #{zm_.inspect} but factory doesn't support Z."
106
+ end
107
+ if expect_m_ && !@cur_factory_support_m
108
+ raise Errors::ParseError, "Type tag declares #{zm_.inspect} but factory doesn't support M."
109
+ end
110
+ case type_
111
+ when 'point'
112
+ _parse_point(expect_z_, expect_m_, true)
113
+ when 'linestring'
114
+ _parse_line_string(expect_z_, expect_m_)
115
+ when 'polygon'
116
+ _parse_polygon(expect_z_, expect_m_)
117
+ when 'geometrycollection'
118
+ _parse_geometry_collection(expect_z_, expect_m_)
119
+ when 'multipoint'
120
+ _parse_multi_point(expect_z_, expect_m_)
121
+ when 'multilinestring'
122
+ _parse_multi_line_string(expect_z_, expect_m_)
123
+ when 'multipolygon'
124
+ _parse_multi_polygon(expect_z_, expect_m_)
125
+ else
126
+ raise Errors::ParseError, "Unknown type tag: #{@cur_token.inspect}."
127
+ end
128
+ end
129
+
130
+
131
+ def _parse_coords(expect_z_, expect_m_) # :nodoc:
132
+ _expect_token_type(::Numeric)
133
+ x_ = @cur_token
134
+ _next_token
135
+ _expect_token_type(::Numeric)
136
+ y_ = @cur_token
137
+ _next_token
138
+ extra_ = []
139
+ if expect_z_.nil?
140
+ while ::Numeric === @cur_token
141
+ extra_ << @cur_token
142
+ _next_token
143
+ end
144
+ else
145
+ if expect_z_
146
+ _expect_token_type(::Numeric)
147
+ extra_ << @cur_token
148
+ _next_token
149
+ end
150
+ if expect_m_
151
+ _expect_token_type(::Numeric)
152
+ extra_ << @cur_token
153
+ _next_token
154
+ end
155
+ end
156
+ @cur_factory.point(x_, y_, *extra_)
157
+ end
158
+
159
+
160
+ def _parse_point(expect_z_, expect_m_, convert_empty_=false) # :nodoc:
161
+ if convert_empty_ && @cur_token == 'empty'
162
+ point_ = @cur_factory.multi_point([])
163
+ else
164
+ _expect_token_type(:begin)
165
+ _next_token
166
+ point_ = _parse_coords(expect_z_, expect_m_)
167
+ _expect_token_type(:end)
168
+ end
169
+ _next_token
170
+ point_
171
+ end
172
+
173
+
174
+ def _parse_line_string(expect_z_, expect_m_) # :nodoc:
175
+ points_ = []
176
+ if @cur_token != 'empty'
177
+ _expect_token_type(:begin)
178
+ _next_token
179
+ loop do
180
+ points_ << _parse_coords(expect_z_, expect_m_)
181
+ break if @cur_token == :end
182
+ _expect_token_type(:comma)
183
+ _next_token
184
+ end
185
+ end
186
+ _next_token
187
+ @cur_factory.line_string(points_)
188
+ end
189
+
190
+
191
+ def _parse_polygon(expect_z_, expect_m_) # :nodoc:
192
+ inner_rings_ = []
193
+ if @cur_token == 'empty'
194
+ outer_ring_ = @cur_factory.linear_ring([])
195
+ else
196
+ _expect_token_type(:begin)
197
+ _next_token
198
+ outer_ring_ = _parse_line_string(expect_z_, expect_m_)
199
+ loop do
200
+ break if @cur_token == :end
201
+ _expect_token_type(:comma)
202
+ _next_token
203
+ inner_rings_ << _parse_line_string(expect_z_, expect_m_)
204
+ end
205
+ end
206
+ _next_token
207
+ @cur_factory.polygon(outer_ring_, inner_rings_)
208
+ end
209
+
210
+
211
+ def _parse_geometry_collection(expect_z_, expect_m_) # :nodoc:
212
+ geometries_ = []
213
+ if @cur_token != 'empty'
214
+ _expect_token_type(:begin)
215
+ _next_token
216
+ loop do
217
+ geometries_ << _parse_type_tag(expect_z_, expect_m_)
218
+ break if @cur_token == :end
219
+ _expect_token_type(:comma)
220
+ _next_token
221
+ end
222
+ end
223
+ _next_token
224
+ @cur_factory.collection(geometries_)
225
+ end
226
+
227
+
228
+ def _parse_multi_point(expect_z_, expect_m_) # :nodoc:
229
+ points_ = []
230
+ if @cur_token != 'empty'
231
+ _expect_token_type(:begin)
232
+ _next_token
233
+ loop do
234
+ points_ << _parse_point(expect_z_, expect_m_)
235
+ break if @cur_token == :end
236
+ _expect_token_type(:comma)
237
+ _next_token
238
+ end
239
+ end
240
+ _next_token
241
+ @cur_factory.multi_point(points_)
242
+ end
243
+
244
+
245
+ def _parse_multi_line_string(expect_z_, expect_m_) # :nodoc:
246
+ line_strings_ = []
247
+ if @cur_token != 'empty'
248
+ _expect_token_type(:begin)
249
+ _next_token
250
+ loop do
251
+ line_strings_ << _parse_line_string(expect_z_, expect_m_)
252
+ break if @cur_token == :end
253
+ _expect_token_type(:comma)
254
+ _next_token
255
+ end
256
+ end
257
+ _next_token
258
+ @cur_factory.multi_line_string(line_strings_)
259
+ end
260
+
261
+
262
+ def _parse_multi_polygon(expect_z_, expect_m_) # :nodoc:
263
+ polygons_ = []
264
+ if @cur_token != 'empty'
265
+ _expect_token_type(:begin)
266
+ _next_token
267
+ loop do
268
+ polygons_ << _parse_polygon(expect_z_, expect_m_)
269
+ break if @cur_token == :end
270
+ _expect_token_type(:comma)
271
+ _next_token
272
+ end
273
+ end
274
+ _next_token
275
+ @cur_factory.multi_polygon(polygons_)
276
+ end
277
+
278
+
279
+ def _start_scanner(str_) # :nodoc:
280
+ @_scanner = ::StringScanner.new(str_)
281
+ _next_token
282
+ end
283
+
284
+
285
+ def _expect_token_type(type_) # :nodoc:
286
+ unless type_ === @cur_token
287
+ raise Errors::ParseError, "#{type_.inspect} expected but #{@cur_token.inspect} found."
288
+ end
289
+ end
290
+
291
+
292
+ def _next_token(expect_=nil) # :nodoc:
293
+ if @_scanner.scan_until(/\(|\)|\[|\]|,|[^\s\(\)\[\],]+/)
294
+ token_ = @_scanner.matched
295
+ case token_
296
+ when /^[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?$/
297
+ @cur_token = token_.to_f
298
+ when /^[a-z]+$/
299
+ @cur_token = token_
300
+ when ','
301
+ @cur_token = :comma
302
+ when '(','['
303
+ @cur_token = :begin
304
+ when ']',')'
305
+ @cur_token = :end
306
+ else
307
+ raise Errors::ParseError, "Bad token: #{token_.inspect}"
308
+ end
309
+ else
310
+ @cur_token = nil
311
+ end
312
+ @cur_token
313
+ end
314
+
315
+
316
+ end
317
+
318
+
319
+ end
320
+
321
+ end