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.
- data/History.rdoc +11 -0
- data/Version +1 -1
- data/ext/geos_c_impl/factory.c +35 -40
- data/ext/geos_c_impl/factory.h +4 -1
- data/ext/geos_c_impl/geometry_collection.c +5 -5
- data/ext/geos_c_impl/geometry_collection.h +1 -1
- data/ext/geos_c_impl/line_string.c +129 -116
- data/ext/geos_c_impl/point.c +22 -33
- data/ext/geos_c_impl/point.h +1 -6
- data/ext/geos_c_impl/polygon.c +4 -4
- data/ext/geos_c_impl/polygon.h +1 -1
- data/lib/rgeo.rb +1 -0
- data/lib/rgeo/cartesian/simple_factory.rb +20 -4
- data/lib/rgeo/errors.rb +8 -0
- data/lib/rgeo/features/factory.rb +35 -1
- data/lib/rgeo/features/point.rb +22 -0
- data/lib/rgeo/features/polygon.rb +1 -2
- data/lib/rgeo/features/types.rb +70 -16
- data/lib/rgeo/geography/factories.rb +40 -3
- data/lib/rgeo/geography/factory.rb +25 -5
- data/lib/rgeo/geography/simple_mercator/feature_methods.rb +2 -6
- data/lib/rgeo/geography/simple_mercator/projector.rb +1 -1
- data/lib/rgeo/geos/factory.rb +40 -46
- data/lib/rgeo/geos/interface.rb +10 -0
- data/lib/rgeo/impl_helpers.rb +0 -1
- data/lib/rgeo/impl_helpers/basic_geometry_methods.rb +2 -2
- data/lib/rgeo/impl_helpers/basic_point_methods.rb +12 -14
- data/lib/rgeo/wkrep.rb +59 -0
- data/lib/rgeo/wkrep/wkb_generator.rb +181 -0
- data/lib/rgeo/wkrep/wkb_parser.rb +205 -0
- data/lib/rgeo/wkrep/wkt_generator.rb +187 -0
- data/lib/rgeo/wkrep/wkt_parser.rb +321 -0
- data/tests/common/line_string_tests.rb +26 -2
- data/tests/common/point_tests.rb +26 -0
- data/tests/geos/tc_point.rb +1 -20
- data/tests/simple_cartesian/tc_point.rb +1 -0
- data/tests/simple_mercator/tc_point.rb +1 -0
- data/tests/simple_spherical/tc_point.rb +1 -0
- data/tests/tc_oneoff.rb +3 -2
- metadata +8 -4
- 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
|