rgeo 2.3.1 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/README.md +23 -14
- data/ext/geos_c_impl/analysis.c +30 -25
- data/ext/geos_c_impl/analysis.h +8 -7
- data/ext/geos_c_impl/coordinates.c +27 -21
- data/ext/geos_c_impl/coordinates.h +5 -2
- data/ext/geos_c_impl/errors.c +19 -10
- data/ext/geos_c_impl/errors.h +11 -4
- data/ext/geos_c_impl/extconf.rb +42 -28
- data/ext/geos_c_impl/factory.c +540 -451
- data/ext/geos_c_impl/factory.h +105 -95
- data/ext/geos_c_impl/geometry.c +593 -387
- data/ext/geos_c_impl/geometry.h +10 -5
- data/ext/geos_c_impl/geometry_collection.c +306 -339
- data/ext/geos_c_impl/geometry_collection.h +6 -20
- data/ext/geos_c_impl/globals.c +169 -0
- data/ext/geos_c_impl/globals.h +46 -0
- data/ext/geos_c_impl/line_string.c +271 -231
- data/ext/geos_c_impl/line_string.h +5 -8
- data/ext/geos_c_impl/main.c +16 -16
- data/ext/geos_c_impl/point.c +65 -67
- data/ext/geos_c_impl/point.h +4 -7
- data/ext/geos_c_impl/polygon.c +137 -135
- data/ext/geos_c_impl/polygon.h +11 -11
- data/ext/geos_c_impl/preface.h +16 -10
- data/ext/geos_c_impl/ruby_more.c +67 -0
- data/ext/geos_c_impl/ruby_more.h +25 -0
- data/lib/rgeo/cartesian/analysis.rb +5 -3
- data/lib/rgeo/cartesian/bounding_box.rb +74 -79
- data/lib/rgeo/cartesian/calculations.rb +64 -33
- data/lib/rgeo/cartesian/factory.rb +57 -102
- data/lib/rgeo/cartesian/feature_classes.rb +68 -46
- data/lib/rgeo/cartesian/feature_methods.rb +67 -25
- data/lib/rgeo/cartesian/interface.rb +6 -41
- data/lib/rgeo/cartesian/planar_graph.rb +373 -0
- data/lib/rgeo/cartesian/sweepline_intersector.rb +147 -0
- data/lib/rgeo/cartesian/valid_op.rb +69 -0
- data/lib/rgeo/cartesian.rb +3 -0
- data/lib/rgeo/coord_sys/cs/entities.rb +303 -99
- data/lib/rgeo/coord_sys/cs/factories.rb +0 -2
- data/lib/rgeo/coord_sys/cs/wkt_parser.rb +90 -42
- data/lib/rgeo/coord_sys.rb +1 -20
- data/lib/rgeo/error.rb +15 -0
- data/lib/rgeo/feature/curve.rb +0 -11
- data/lib/rgeo/feature/factory.rb +26 -36
- data/lib/rgeo/feature/factory_generator.rb +6 -14
- data/lib/rgeo/feature/geometry.rb +146 -66
- data/lib/rgeo/feature/geometry_collection.rb +16 -9
- data/lib/rgeo/feature/line_string.rb +4 -5
- data/lib/rgeo/feature/linear_ring.rb +0 -1
- data/lib/rgeo/feature/multi_curve.rb +0 -6
- data/lib/rgeo/feature/multi_surface.rb +3 -4
- data/lib/rgeo/feature/point.rb +4 -5
- data/lib/rgeo/feature/polygon.rb +1 -2
- data/lib/rgeo/feature/surface.rb +3 -4
- data/lib/rgeo/feature/types.rb +69 -85
- data/lib/rgeo/geographic/factory.rb +98 -125
- data/lib/rgeo/geographic/interface.rb +69 -166
- data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
- data/lib/rgeo/geographic/projected_feature_methods.rb +67 -42
- data/lib/rgeo/geographic/projected_window.rb +36 -22
- data/lib/rgeo/geographic/{proj4_projector.rb → projector.rb} +3 -5
- data/lib/rgeo/geographic/simple_mercator_projector.rb +26 -25
- data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
- data/lib/rgeo/geographic/spherical_feature_methods.rb +86 -9
- data/lib/rgeo/geographic/spherical_math.rb +17 -20
- data/lib/rgeo/geographic.rb +1 -1
- data/lib/rgeo/geos/capi_factory.rb +87 -158
- data/lib/rgeo/geos/capi_feature_classes.rb +50 -36
- data/lib/rgeo/geos/ffi_factory.rb +105 -173
- data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
- data/lib/rgeo/geos/ffi_feature_methods.rb +105 -127
- data/lib/rgeo/geos/interface.rb +20 -59
- data/lib/rgeo/geos/utils.rb +5 -5
- data/lib/rgeo/geos/zm_factory.rb +53 -95
- data/lib/rgeo/geos/zm_feature_methods.rb +30 -33
- data/lib/rgeo/geos.rb +8 -8
- data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +9 -22
- data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -2
- data/lib/rgeo/impl_helper/basic_line_string_methods.rb +28 -56
- data/lib/rgeo/impl_helper/basic_point_methods.rb +2 -14
- data/lib/rgeo/impl_helper/basic_polygon_methods.rb +17 -26
- data/lib/rgeo/impl_helper/utils.rb +21 -0
- data/lib/rgeo/impl_helper/valid_op.rb +350 -0
- data/lib/rgeo/impl_helper/validity_check.rb +139 -0
- data/lib/rgeo/impl_helper.rb +1 -0
- data/lib/rgeo/version.rb +1 -1
- data/lib/rgeo/wkrep/wkb_generator.rb +73 -63
- data/lib/rgeo/wkrep/wkb_parser.rb +33 -31
- data/lib/rgeo/wkrep/wkt_generator.rb +52 -45
- data/lib/rgeo/wkrep/wkt_parser.rb +48 -35
- data/lib/rgeo.rb +1 -3
- metadata +50 -13
- data/lib/rgeo/coord_sys/srs_database/entry.rb +0 -107
- data/lib/rgeo/coord_sys/srs_database/sr_org.rb +0 -64
- 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
|
data/lib/rgeo/impl_helper.rb
CHANGED
data/lib/rgeo/version.rb
CHANGED
@@ -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
|
-
|
104
|
-
|
102
|
+
has_z = factory.property(:has_z_coordinate)
|
103
|
+
has_m = factory.property(:has_m_coordinate)
|
105
104
|
else
|
106
|
-
|
107
|
-
|
105
|
+
has_z = false
|
106
|
+
has_m = false
|
108
107
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
118
|
-
|
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
|
-
|
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
|
-
|
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 { |
|
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
|
140
|
-
array << obj.m if
|
163
|
+
array << obj.z if rval.z?
|
164
|
+
array << obj.m if rval.m?
|
141
165
|
array
|
142
166
|
end
|
143
167
|
|
144
|
-
def
|
145
|
-
@
|
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
|
-
|
163
|
-
|
164
|
-
type_code |=
|
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
|
-
|
170
|
-
type_code += 1000 if
|
171
|
-
type_code += 2000 if
|
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
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
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 =
|
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
|
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
|
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.
|
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.
|
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 =
|
56
|
-
|
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
|
-
|
97
|
-
|
97
|
+
support_z = false
|
98
|
+
support_m = false
|
98
99
|
else
|
99
|
-
|
100
|
-
|
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
|
-
|
103
|
+
str = generate_feature(obj, support_z, support_m, toplevel: true)
|
104
|
+
case @convert_case
|
105
|
+
when :upper
|
104
106
|
str.upcase
|
105
|
-
|
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
|
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
|
-
|
119
|
-
|
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
|
-
|
122
|
-
if
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
154
|
-
str << " #{obj.m}" if
|
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
|
-
|
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
|
-
|
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
|