rgeo 0.1.13 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.
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