rgeo 2.3.0 → 3.0.0.pre.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/README.md +1 -0
- data/ext/geos_c_impl/analysis.c +8 -6
- data/ext/geos_c_impl/analysis.h +1 -3
- data/ext/geos_c_impl/errors.c +10 -8
- data/ext/geos_c_impl/errors.h +7 -3
- data/ext/geos_c_impl/extconf.rb +3 -0
- data/ext/geos_c_impl/factory.c +251 -182
- data/ext/geos_c_impl/factory.h +43 -62
- data/ext/geos_c_impl/geometry.c +56 -24
- data/ext/geos_c_impl/geometry.h +8 -3
- data/ext/geos_c_impl/geometry_collection.c +41 -148
- data/ext/geos_c_impl/geometry_collection.h +1 -14
- data/ext/geos_c_impl/globals.c +91 -0
- data/ext/geos_c_impl/globals.h +45 -0
- data/ext/geos_c_impl/line_string.c +28 -29
- data/ext/geos_c_impl/line_string.h +1 -3
- data/ext/geos_c_impl/main.c +10 -9
- data/ext/geos_c_impl/point.c +9 -8
- data/ext/geos_c_impl/point.h +1 -3
- data/ext/geos_c_impl/polygon.c +15 -51
- data/ext/geos_c_impl/polygon.h +1 -3
- data/ext/geos_c_impl/preface.h +8 -0
- data/lib/rgeo/cartesian/analysis.rb +2 -2
- data/lib/rgeo/cartesian/calculations.rb +54 -17
- data/lib/rgeo/cartesian/factory.rb +0 -7
- data/lib/rgeo/cartesian/feature_classes.rb +66 -46
- data/lib/rgeo/cartesian/feature_methods.rb +56 -20
- data/lib/rgeo/cartesian/interface.rb +0 -6
- data/lib/rgeo/cartesian/planar_graph.rb +379 -0
- data/lib/rgeo/cartesian/sweepline_intersector.rb +149 -0
- data/lib/rgeo/cartesian/valid_op.rb +71 -0
- data/lib/rgeo/cartesian.rb +3 -0
- data/lib/rgeo/coord_sys/cs/wkt_parser.rb +6 -6
- data/lib/rgeo/error.rb +15 -0
- data/lib/rgeo/feature/curve.rb +12 -2
- data/lib/rgeo/feature/geometry.rb +38 -28
- data/lib/rgeo/feature/geometry_collection.rb +13 -5
- data/lib/rgeo/feature/line_string.rb +3 -3
- data/lib/rgeo/feature/multi_curve.rb +6 -1
- data/lib/rgeo/feature/multi_surface.rb +3 -3
- data/lib/rgeo/feature/point.rb +4 -4
- data/lib/rgeo/feature/surface.rb +3 -3
- data/lib/rgeo/geographic/factory.rb +0 -7
- data/lib/rgeo/geographic/interface.rb +4 -18
- data/lib/rgeo/geographic/proj4_projector.rb +0 -2
- data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
- data/lib/rgeo/geographic/projected_feature_methods.rb +63 -30
- data/lib/rgeo/geographic/simple_mercator_projector.rb +0 -2
- data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
- data/lib/rgeo/geographic/spherical_feature_methods.rb +68 -2
- data/lib/rgeo/geos/capi_factory.rb +21 -31
- data/lib/rgeo/geos/capi_feature_classes.rb +64 -11
- data/lib/rgeo/geos/ffi_factory.rb +0 -28
- data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
- data/lib/rgeo/geos/ffi_feature_methods.rb +53 -10
- data/lib/rgeo/geos/interface.rb +18 -10
- data/lib/rgeo/geos/zm_factory.rb +0 -12
- data/lib/rgeo/geos/zm_feature_methods.rb +30 -5
- data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +18 -8
- data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -1
- data/lib/rgeo/impl_helper/basic_line_string_methods.rb +37 -26
- data/lib/rgeo/impl_helper/basic_point_methods.rb +13 -3
- data/lib/rgeo/impl_helper/basic_polygon_methods.rb +8 -3
- data/lib/rgeo/impl_helper/valid_op.rb +354 -0
- data/lib/rgeo/impl_helper/validity_check.rb +138 -0
- data/lib/rgeo/impl_helper.rb +1 -0
- data/lib/rgeo/version.rb +1 -1
- data/lib/rgeo/wkrep/wkb_generator.rb +1 -1
- data/lib/rgeo/wkrep/wkt_generator.rb +6 -6
- metadata +30 -7
@@ -42,7 +42,6 @@ module RGeo
|
|
42
42
|
end
|
43
43
|
srid ||= @coord_sys.authority_code if @coord_sys
|
44
44
|
@srid = srid.to_i
|
45
|
-
@lenient_assertions = opts[:uses_lenient_assertions] ? true : false
|
46
45
|
@buffer_resolution = opts[:buffer_resolution].to_i
|
47
46
|
@buffer_resolution = 1 if @buffer_resolution < 1
|
48
47
|
|
@@ -103,7 +102,6 @@ module RGeo
|
|
103
102
|
"wkbg" => @wkb_generator.properties,
|
104
103
|
"wktp" => @wkt_parser.properties,
|
105
104
|
"wkbp" => @wkb_parser.properties,
|
106
|
-
"lena" => @lenient_assertions,
|
107
105
|
"bufr" => @buffer_resolution
|
108
106
|
}
|
109
107
|
hash_["proj4"] = @proj4.marshal_dump if @proj4
|
@@ -131,7 +129,6 @@ module RGeo
|
|
131
129
|
wkb_generator: symbolize_hash(data["wkbg"]),
|
132
130
|
wkt_parser: symbolize_hash(data["wktp"]),
|
133
131
|
wkb_parser: symbolize_hash(data["wkbp"]),
|
134
|
-
uses_lenient_assertions: data["lena"],
|
135
132
|
buffer_resolution: data["bufr"],
|
136
133
|
proj4: proj4,
|
137
134
|
coord_sys: coord_sys
|
@@ -144,7 +141,6 @@ module RGeo
|
|
144
141
|
coder["has_z_coordinate"] = @has_z
|
145
142
|
coder["has_m_coordinate"] = @has_m
|
146
143
|
coder["srid"] = @srid
|
147
|
-
coder["lenient_assertions"] = @lenient_assertions
|
148
144
|
coder["buffer_resolution"] = @buffer_resolution
|
149
145
|
coder["wkt_generator"] = @wkt_generator.properties
|
150
146
|
coder["wkb_generator"] = @wkb_generator.properties
|
@@ -180,7 +176,6 @@ module RGeo
|
|
180
176
|
wkb_generator: symbolize_hash(coder["wkb_generator"]),
|
181
177
|
wkt_parser: symbolize_hash(coder["wkt_parser"]),
|
182
178
|
wkb_parser: symbolize_hash(coder["wkb_parser"]),
|
183
|
-
uses_lenient_assertions: coder["lenient_assertions"],
|
184
179
|
buffer_resolution: coder["buffer_resolution"],
|
185
180
|
proj4: proj4,
|
186
181
|
coord_sys: coord_sys
|
@@ -199,8 +194,6 @@ module RGeo
|
|
199
194
|
@has_z
|
200
195
|
when :has_m_coordinate
|
201
196
|
@has_m
|
202
|
-
when :uses_lenient_assertions
|
203
|
-
@lenient_assertions
|
204
197
|
when :buffer_resolution
|
205
198
|
@buffer_resolution
|
206
199
|
when :is_cartesian
|
@@ -9,76 +9,96 @@
|
|
9
9
|
module RGeo
|
10
10
|
module Cartesian
|
11
11
|
class PointImpl # :nodoc:
|
12
|
-
include
|
13
|
-
include
|
14
|
-
include
|
15
|
-
include
|
16
|
-
include
|
12
|
+
include Feature::Point
|
13
|
+
include ImplHelper::ValidityCheck
|
14
|
+
include ImplHelper::BasicGeometryMethods
|
15
|
+
include ImplHelper::BasicPointMethods
|
16
|
+
include ImplHelper::ValidOp
|
17
|
+
include GeometryMethods
|
18
|
+
include PointMethods
|
17
19
|
end
|
18
20
|
|
19
21
|
class LineStringImpl # :nodoc:
|
20
|
-
include
|
21
|
-
include
|
22
|
-
include
|
23
|
-
include
|
24
|
-
include
|
22
|
+
include Feature::LineString
|
23
|
+
include ImplHelper::ValidityCheck
|
24
|
+
include ImplHelper::BasicGeometryMethods
|
25
|
+
include ImplHelper::BasicLineStringMethods
|
26
|
+
include ImplHelper::ValidOp
|
27
|
+
include GeometryMethods
|
28
|
+
include LineStringMethods
|
25
29
|
end
|
26
30
|
|
27
31
|
class LineImpl # :nodoc:
|
28
|
-
include
|
29
|
-
include
|
30
|
-
include
|
31
|
-
include
|
32
|
-
include
|
33
|
-
include
|
32
|
+
include Feature::Line
|
33
|
+
include ImplHelper::ValidityCheck
|
34
|
+
include ImplHelper::BasicGeometryMethods
|
35
|
+
include ImplHelper::BasicLineStringMethods
|
36
|
+
include ImplHelper::BasicLineMethods
|
37
|
+
include ImplHelper::ValidOp
|
38
|
+
include GeometryMethods
|
39
|
+
include LineStringMethods
|
34
40
|
end
|
35
41
|
|
36
42
|
class LinearRingImpl # :nodoc:
|
37
|
-
include
|
38
|
-
include
|
39
|
-
include
|
40
|
-
include
|
41
|
-
include
|
42
|
-
include
|
43
|
+
include Feature::LinearRing
|
44
|
+
include ImplHelper::ValidityCheck
|
45
|
+
include ImplHelper::BasicGeometryMethods
|
46
|
+
include ImplHelper::BasicLineStringMethods
|
47
|
+
include ImplHelper::BasicLinearRingMethods
|
48
|
+
include ImplHelper::ValidOp
|
49
|
+
include GeometryMethods
|
50
|
+
include LineStringMethods
|
43
51
|
end
|
44
52
|
|
45
53
|
class PolygonImpl # :nodoc:
|
46
|
-
include
|
47
|
-
include
|
48
|
-
include
|
49
|
-
include
|
54
|
+
include Feature::Polygon
|
55
|
+
include ImplHelper::ValidityCheck
|
56
|
+
include ImplHelper::BasicGeometryMethods
|
57
|
+
include ImplHelper::BasicPolygonMethods
|
58
|
+
include ValidOp
|
59
|
+
include GeometryMethods
|
50
60
|
end
|
51
61
|
|
52
62
|
class GeometryCollectionImpl # :nodoc:
|
53
|
-
include
|
54
|
-
include
|
55
|
-
include
|
56
|
-
include
|
63
|
+
include Feature::GeometryCollection
|
64
|
+
include ImplHelper::ValidityCheck
|
65
|
+
include ImplHelper::BasicGeometryMethods
|
66
|
+
include ImplHelper::BasicGeometryCollectionMethods
|
67
|
+
include ImplHelper::ValidOp
|
68
|
+
include GeometryMethods
|
57
69
|
end
|
58
70
|
|
59
71
|
class MultiPointImpl # :nodoc:
|
60
|
-
include
|
61
|
-
include
|
62
|
-
include
|
63
|
-
include
|
64
|
-
include
|
72
|
+
include Feature::MultiPoint
|
73
|
+
include ImplHelper::ValidityCheck
|
74
|
+
include ImplHelper::BasicGeometryMethods
|
75
|
+
include ImplHelper::BasicGeometryCollectionMethods
|
76
|
+
include ImplHelper::BasicMultiPointMethods
|
77
|
+
include ImplHelper::ValidOp
|
78
|
+
include GeometryMethods
|
65
79
|
end
|
66
80
|
|
67
81
|
class MultiLineStringImpl # :nodoc:
|
68
|
-
include
|
69
|
-
include
|
70
|
-
include
|
71
|
-
include
|
72
|
-
include
|
73
|
-
include
|
82
|
+
include Feature::MultiLineString
|
83
|
+
include ImplHelper::ValidityCheck
|
84
|
+
include ImplHelper::BasicGeometryMethods
|
85
|
+
include ImplHelper::BasicGeometryCollectionMethods
|
86
|
+
include ImplHelper::BasicMultiLineStringMethods
|
87
|
+
include ImplHelper::ValidOp
|
88
|
+
include GeometryMethods
|
89
|
+
include MultiLineStringMethods
|
74
90
|
end
|
75
91
|
|
76
92
|
class MultiPolygonImpl # :nodoc:
|
77
|
-
include
|
78
|
-
include
|
79
|
-
include
|
80
|
-
include
|
81
|
-
include
|
93
|
+
include Feature::MultiPolygon
|
94
|
+
include ImplHelper::ValidityCheck
|
95
|
+
include ImplHelper::BasicGeometryMethods
|
96
|
+
include ImplHelper::BasicGeometryCollectionMethods
|
97
|
+
include ImplHelper::BasicMultiPolygonMethods
|
98
|
+
include ImplHelper::ValidOp
|
99
|
+
include GeometryMethods
|
82
100
|
end
|
101
|
+
|
102
|
+
ImplHelper::ValidityCheck.override_classes
|
83
103
|
end
|
84
104
|
end
|
@@ -16,6 +16,12 @@ module RGeo
|
|
16
16
|
def envelope
|
17
17
|
BoundingBox.new(factory).add(self).to_geometry
|
18
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def graph
|
23
|
+
@graph ||= GeometryGraph.new(self)
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
27
|
module PointMethods # :nodoc:
|
@@ -49,32 +55,62 @@ module RGeo
|
|
49
55
|
end
|
50
56
|
end
|
51
57
|
|
58
|
+
def simple?
|
59
|
+
# Use a SweeplineIntersector to determine if there are any self-intersections
|
60
|
+
# in the ring. The GeometryGraph of the ring could be used by comparing the
|
61
|
+
# edges to number of segments (graph.incident_edges.length == segments.length),
|
62
|
+
# but this adds computational and memory overhead if graph isn't already memoized.
|
63
|
+
# Since graph is not used elsewhere in LineStringMethods, we will just use the
|
64
|
+
# SweeplineIntersector for now.
|
65
|
+
li = SweeplineIntersector.new(segments)
|
66
|
+
li.proper_intersections.empty?
|
67
|
+
end
|
68
|
+
|
52
69
|
def is_simple?
|
53
|
-
|
54
|
-
|
55
|
-
return true if len == 1
|
56
|
-
return segments[0].s != segments[1].e if len == 2
|
57
|
-
segments.each_with_index do |seg, index|
|
58
|
-
nindex = index + 1
|
59
|
-
nindex = nil if nindex == len
|
60
|
-
return false if nindex && seg.contains_point?(segments[nindex].e)
|
61
|
-
pindex = index - 1
|
62
|
-
pindex = nil if pindex < 0
|
63
|
-
return false if pindex && seg.contains_point?(segments[pindex].s)
|
64
|
-
next unless nindex
|
65
|
-
oindex = nindex + 1
|
66
|
-
while oindex < len
|
67
|
-
oseg = segments[oindex]
|
68
|
-
return false if !(index == 0 && oindex == len - 1 && seg.s == oseg.e) && seg.intersects_segment?(oseg)
|
69
|
-
oindex += 1
|
70
|
-
end
|
71
|
-
end
|
72
|
-
true
|
70
|
+
warn "The is_simple? method is deprecated, please use the simple? counterpart, will be removed in v3" unless ENV["RGEO_SILENCE_DEPRECATION"]
|
71
|
+
simple?
|
73
72
|
end
|
74
73
|
|
75
74
|
def length
|
76
75
|
segments.inject(0.0) { |sum, seg| sum + seg.length }
|
77
76
|
end
|
77
|
+
|
78
|
+
def crosses?(rhs)
|
79
|
+
case rhs
|
80
|
+
when Feature::LineString
|
81
|
+
crosses_line_string?(rhs)
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Determines if a cross occurs with another linestring.
|
90
|
+
# Process is to get the number of proper intersections in each geom
|
91
|
+
# then overlay and get the number of proper intersections from that.
|
92
|
+
# If the overlaid number is higher than the sum of individual self-ints
|
93
|
+
# then there is an intersection. Finally, we need to check the intersection
|
94
|
+
# to see that it is not a boundary point of either segment.
|
95
|
+
#
|
96
|
+
# @param rhs [Feature::LineString]
|
97
|
+
#
|
98
|
+
# @return [Boolean]
|
99
|
+
def crosses_line_string?(rhs)
|
100
|
+
self_ints = SweeplineIntersector.new(segments).proper_intersections
|
101
|
+
self_ints += SweeplineIntersector.new(rhs.segments).proper_intersections
|
102
|
+
overlay_ints = SweeplineIntersector.new(segments + rhs.segments).proper_intersections
|
103
|
+
|
104
|
+
(overlay_ints - self_ints).each do |int|
|
105
|
+
s1s = int.s1.s
|
106
|
+
s1e = int.s1.e
|
107
|
+
s2s = int.s2.s
|
108
|
+
s2e = int.s2.e
|
109
|
+
return true unless [s1s, s1e, s2s, s2e].include?(int.point)
|
110
|
+
end
|
111
|
+
|
112
|
+
false
|
113
|
+
end
|
78
114
|
end
|
79
115
|
|
80
116
|
module MultiLineStringMethods # :nodoc:
|
@@ -78,12 +78,6 @@ module RGeo
|
|
78
78
|
# Support a Z coordinate. Default is false.
|
79
79
|
# [<tt>:has_m_coordinate</tt>]
|
80
80
|
# Support an M coordinate. Default is false.
|
81
|
-
# [<tt>:uses_lenient_assertions</tt>]
|
82
|
-
# If set to true, assertion checking is disabled. This includes
|
83
|
-
# simplicity checking on LinearRing, and validity checks on
|
84
|
-
# Polygon and MultiPolygon. This may speed up creation of certain
|
85
|
-
# objects, at the expense of not doing the proper checking for
|
86
|
-
# OGC compliance. Default is false.
|
87
81
|
# [<tt>:wkt_parser</tt>]
|
88
82
|
# Configure the parser for WKT. The value is a hash of
|
89
83
|
# configuration parameters for WKRep::WKTParser.new. Default is
|
@@ -0,0 +1,379 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RGeo
|
4
|
+
module Cartesian
|
5
|
+
# A Doubly Connected Edge List (DCEL) implementation of a Planar Graph.
|
6
|
+
# It represents geometries as vertices and half-edges.
|
7
|
+
#
|
8
|
+
# It includes an incident_edges hash that maps vertices to an array
|
9
|
+
# of half-edges whose origins are that vertex.
|
10
|
+
#
|
11
|
+
# Upon instantiation, the graph will compute the intersections using
|
12
|
+
# the SweeplineIntersector, populate the incident_edges map, and
|
13
|
+
# link all cyclic edges.
|
14
|
+
class PlanarGraph
|
15
|
+
# HalfEdge represents an edge as 2 directed edges.
|
16
|
+
# One half-edge will have it's origin at edge.s, the other
|
17
|
+
# at edge.e. Both half-edges will be linked as each other's twins.
|
18
|
+
#
|
19
|
+
# HalfEdges also contain references to the next and prev half-edges,
|
20
|
+
# where next's origin is this half-edges destination. Prev's destination
|
21
|
+
# is this half-edge's origin.
|
22
|
+
class HalfEdge
|
23
|
+
include Comparable
|
24
|
+
|
25
|
+
# Creates 2 half edges from an edge.
|
26
|
+
# They will be assigned as each other's twins.
|
27
|
+
# The Half Edges will be returned in the order of points
|
28
|
+
# of the edge (start, end).
|
29
|
+
#
|
30
|
+
# @param edge [RGeo::Cartesian::Segment]
|
31
|
+
#
|
32
|
+
# @return [Array]
|
33
|
+
def self.from_edge(edge)
|
34
|
+
e1 = new(edge.s)
|
35
|
+
e2 = new(edge.e)
|
36
|
+
|
37
|
+
e1.twin = e2
|
38
|
+
e2.twin = e1
|
39
|
+
[e1, e2]
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(origin)
|
43
|
+
@origin = origin
|
44
|
+
@twin = nil
|
45
|
+
@next = nil
|
46
|
+
@prev = nil
|
47
|
+
end
|
48
|
+
attr_reader :origin
|
49
|
+
attr_accessor :twin, :next, :prev
|
50
|
+
|
51
|
+
# HalfEdges will be sorted around their shared vertex
|
52
|
+
# in a CW fashion. This means that face interiors will be
|
53
|
+
# a CCW.
|
54
|
+
def <=>(other)
|
55
|
+
angle <=> other.angle
|
56
|
+
end
|
57
|
+
|
58
|
+
# Will find attempt to find a cycle starting at this
|
59
|
+
# HalfEdge. Will end upon finding the first repeated HalfEdge
|
60
|
+
# or a HalfEdge where +next+ is nil.
|
61
|
+
#
|
62
|
+
# If a block is given, each HalfEdge seen will be yielded to the block.
|
63
|
+
#
|
64
|
+
# @return [Enumerator]
|
65
|
+
def and_connected
|
66
|
+
return to_enum(__method__) unless block_given?
|
67
|
+
|
68
|
+
hedges = Set.new
|
69
|
+
yield(self)
|
70
|
+
hedges << self
|
71
|
+
|
72
|
+
n = self.next
|
73
|
+
until hedges.include?(n) || n.nil?
|
74
|
+
yield(n)
|
75
|
+
hedges << n
|
76
|
+
n = n.next
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return the destination of the half edge
|
81
|
+
#
|
82
|
+
# @return [RGeo::Feature::Point]
|
83
|
+
def destination
|
84
|
+
twin&.origin
|
85
|
+
end
|
86
|
+
|
87
|
+
# Compute the angle from the positive x-axis.
|
88
|
+
# Used for sorting at each node.
|
89
|
+
#
|
90
|
+
# @return [Float]
|
91
|
+
def angle
|
92
|
+
@angle ||= Math.atan2(destination.y - origin.y, destination.x - origin.x)
|
93
|
+
end
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} #{self}>"
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_s
|
100
|
+
dst = destination
|
101
|
+
pr = prev&.origin
|
102
|
+
n = @next&.origin
|
103
|
+
"HalfEdge(#{origin}, #{dst}), Prev: #{pr}, Next: #{n}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create a new PlanarGraph
|
108
|
+
#
|
109
|
+
# @param edges [Array<RGeo::Cartesian::Segment>] of Segments
|
110
|
+
def initialize(edges = [])
|
111
|
+
@edges = []
|
112
|
+
@incident_edges = {}
|
113
|
+
|
114
|
+
add_edges(edges)
|
115
|
+
end
|
116
|
+
attr_reader :edges, :incident_edges
|
117
|
+
|
118
|
+
# Insert an edge into the graph. This will automatically
|
119
|
+
# calculate intersections and add new vertices if necessary.
|
120
|
+
#
|
121
|
+
# @param edge [RGeo::Cartesian::Segment]
|
122
|
+
def add_edge(edge)
|
123
|
+
@edges << edge
|
124
|
+
create_half_edge(edge)
|
125
|
+
|
126
|
+
# Check if a new intersection was made and handle it.
|
127
|
+
intersection_map.each do |seg, ints|
|
128
|
+
compute_split_edges(seg, ints)
|
129
|
+
end
|
130
|
+
|
131
|
+
link_half_edges
|
132
|
+
end
|
133
|
+
|
134
|
+
# Insert multiple edges into the graph. Like +add_edge+, this automatically
|
135
|
+
# calculates intersections and adds new vertices.
|
136
|
+
#
|
137
|
+
# @param new_edges [Array<RGeo::Cartesian::Segment>]
|
138
|
+
def add_edges(new_edges)
|
139
|
+
@edges.concat(new_edges)
|
140
|
+
new_edges.each do |edge|
|
141
|
+
create_half_edge(edge)
|
142
|
+
end
|
143
|
+
|
144
|
+
intersection_map.each do |seg, ints|
|
145
|
+
compute_split_edges(seg, ints)
|
146
|
+
end
|
147
|
+
|
148
|
+
link_half_edges
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
# Creates a map of +proper_intersections+ for each segment
|
154
|
+
# from a sweepline intersector.
|
155
|
+
#
|
156
|
+
# Can be used to determine which edges need to be split
|
157
|
+
# after adding edges.
|
158
|
+
def intersection_map
|
159
|
+
intersector = SweeplineIntersector.new(edges)
|
160
|
+
intersections = intersector.proper_intersections
|
161
|
+
|
162
|
+
intersection_map = {}
|
163
|
+
intersections.each do |int|
|
164
|
+
# check the int_point against each edge.
|
165
|
+
# if it is not on the boundary of the edge, add it to the
|
166
|
+
# list of intersections for that edge.
|
167
|
+
s1_intersects = int.point != int.s1.s && int.point != int.s1.e
|
168
|
+
if s1_intersects
|
169
|
+
unless intersection_map[int.s1]
|
170
|
+
intersection_map[int.s1] = []
|
171
|
+
end
|
172
|
+
intersection_map[int.s1] << int.point
|
173
|
+
end
|
174
|
+
|
175
|
+
s2_intersects = int.point != int.s2.s && int.point != int.s2.e
|
176
|
+
next unless s2_intersects
|
177
|
+
unless intersection_map[int.s2]
|
178
|
+
intersection_map[int.s2] = []
|
179
|
+
end
|
180
|
+
intersection_map[int.s2] << int.point
|
181
|
+
end
|
182
|
+
intersection_map
|
183
|
+
end
|
184
|
+
|
185
|
+
def create_half_edges
|
186
|
+
@edges.each do |edge|
|
187
|
+
create_half_edge(edge)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def create_half_edge(edge)
|
192
|
+
e1, e2 = HalfEdge.from_edge(edge)
|
193
|
+
insert_half_edge(e1)
|
194
|
+
insert_half_edge(e2)
|
195
|
+
end
|
196
|
+
|
197
|
+
def insert_half_edge(he)
|
198
|
+
unless incident_edges[he.origin.coordinates]
|
199
|
+
@incident_edges[he.origin.coordinates] = []
|
200
|
+
end
|
201
|
+
@incident_edges[he.origin.coordinates] << he
|
202
|
+
end
|
203
|
+
|
204
|
+
# Links all half-edges where possible.
|
205
|
+
# Defines +next+ and +prev+ for every half-edge by rotating
|
206
|
+
# through all half-edges originating at a vertex.
|
207
|
+
#
|
208
|
+
# Assuming half-edges are sorted CCW, every sequential pair of
|
209
|
+
# half-edges (e1, e2) can be linked by saying e1.prev = e2.twin
|
210
|
+
# and e2.twin.next = e1.
|
211
|
+
def link_half_edges
|
212
|
+
incident_edges.each_value do |hedges|
|
213
|
+
hedges.sort!
|
214
|
+
[*hedges, hedges.first].each_cons(2) do |e1, e2|
|
215
|
+
e1.prev = e2.twin
|
216
|
+
e2.twin.next = e1
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# It is possible that intersections occur when new edges are added.
|
222
|
+
# This will split those edges into more half-edges while preserving
|
223
|
+
# the existing half-edges when possible since geometries may reference
|
224
|
+
# them.
|
225
|
+
def compute_split_edges(seg, ints)
|
226
|
+
points = ints.uniq.sort do |a, b|
|
227
|
+
a.distance(seg.s) <=> b.distance(seg.s)
|
228
|
+
end
|
229
|
+
points = [seg.s] + points + [seg.e]
|
230
|
+
|
231
|
+
he_start = incident_edges[points.first.coordinates].find { |he| he.destination == points.last }
|
232
|
+
he_end = incident_edges[points.last.coordinates].find { |he| he.destination == points.first }
|
233
|
+
|
234
|
+
points.each_cons(2) do |s, e|
|
235
|
+
edge = Segment.new(s, e)
|
236
|
+
if s == he_start.origin
|
237
|
+
he = HalfEdge.new(e)
|
238
|
+
he_start.twin = he
|
239
|
+
he.twin = he_start
|
240
|
+
insert_half_edge(he)
|
241
|
+
elsif e == he_end.origin
|
242
|
+
he = HalfEdge.new(s)
|
243
|
+
he_end.twin = he
|
244
|
+
he.twin = he_end
|
245
|
+
insert_half_edge(he)
|
246
|
+
else
|
247
|
+
create_half_edge(edge)
|
248
|
+
end
|
249
|
+
@edges << edge
|
250
|
+
end
|
251
|
+
@edges.delete(seg)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# GeometryGraph is a PlanarGraph that is built by adding
|
256
|
+
# geometries instead of edges. The GeometryGraph will
|
257
|
+
# hold a reference to an arbitrary HalfEdge on the
|
258
|
+
# interior of the geometry for every boundary in the geometry.
|
259
|
+
# For example, a polygon will have a reference to a HalfEdge for its
|
260
|
+
# exterior shell and one for every hole.
|
261
|
+
class GeometryGraph < PlanarGraph
|
262
|
+
# GeomEdge will be used to store the references to the HalfEdges
|
263
|
+
GeomEdge = Struct.new(:exterior_edge, :interior_edges)
|
264
|
+
|
265
|
+
def initialize(geom)
|
266
|
+
super()
|
267
|
+
@parent_geometry = geom
|
268
|
+
@geom_edges = []
|
269
|
+
add_geometry(geom)
|
270
|
+
end
|
271
|
+
attr_reader :parent_geometry, :geom_edges
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
# Adds a geometry to the graph and finds its
|
276
|
+
# reference HalfEdge(s).
|
277
|
+
#
|
278
|
+
# @param geom [RGeo::Feature::Instance]
|
279
|
+
def add_geometry(geom)
|
280
|
+
case geom
|
281
|
+
when Feature::Point
|
282
|
+
# Can't handle points yet, so just add an empty entry for them
|
283
|
+
@geom_edges << GeomEdge.new
|
284
|
+
when Feature::LineString, Feature::LinearRing
|
285
|
+
add_line_string(geom)
|
286
|
+
when Feature::Polygon
|
287
|
+
add_polygon(geom)
|
288
|
+
when Feature::GeometryCollection
|
289
|
+
add_collection(geom)
|
290
|
+
else
|
291
|
+
raise Error::RGeoError, "Invalid Geometry Type: #{geom.class}"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Adds a LineString or LinearRing
|
296
|
+
# to the graph.
|
297
|
+
#
|
298
|
+
# @param geom [RGeo::Feature::LineString]
|
299
|
+
def add_line_string(geom)
|
300
|
+
add_edges(geom.segments)
|
301
|
+
|
302
|
+
hedge = unless geom.empty?
|
303
|
+
@incident_edges[geom.start_point.coordinates].first
|
304
|
+
end
|
305
|
+
|
306
|
+
@geom_edges << GeomEdge.new(hedge, nil)
|
307
|
+
end
|
308
|
+
|
309
|
+
# Adds a Polygon to the graph.
|
310
|
+
#
|
311
|
+
# Will add each shell seperately and find a CCW half-edge
|
312
|
+
# for the exterior ring and a CW half-edge for interior rings
|
313
|
+
# since these are designated as being on the interior of the polygon.
|
314
|
+
#
|
315
|
+
# It is possible that the half-edge will be nil if a suitable one cannot
|
316
|
+
# be found. This indicates the polygon is likely invalid.
|
317
|
+
#
|
318
|
+
# @param geom [RGeo::Feature::Polygon]
|
319
|
+
def add_polygon(geom)
|
320
|
+
exterior = geom.exterior_ring
|
321
|
+
add_edges(exterior.segments)
|
322
|
+
|
323
|
+
hedge = find_hedge(exterior)
|
324
|
+
|
325
|
+
interior_hedges = []
|
326
|
+
geom.interior_rings.each do |interior|
|
327
|
+
add_edges(interior.segments)
|
328
|
+
interior_hedges << find_hedge(interior, ccw: false)
|
329
|
+
end
|
330
|
+
|
331
|
+
@geom_edges << GeomEdge.new(hedge, interior_hedges)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Adds a GeometryCollection to the graph.
|
335
|
+
#
|
336
|
+
# @param col [RGeo::Feature::GeometryCollection]
|
337
|
+
def add_collection(col)
|
338
|
+
col.each do |geom|
|
339
|
+
add_geometry(geom)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Finds a Half-Edge that is part of a CCW or CW rotation
|
344
|
+
# from the input ring. Returns nil if none found.
|
345
|
+
#
|
346
|
+
# Will only consider half-edges that are colinear with
|
347
|
+
# the first or last segments of the ring.
|
348
|
+
#
|
349
|
+
# @param ring [RGeo::Feature::LinearRing]
|
350
|
+
# @param ccw [Boolean] true for CCW, false for CW
|
351
|
+
# @return [HalfEdge, nil]
|
352
|
+
def find_hedge(ring, ccw: true)
|
353
|
+
return nil if ring.num_points.zero?
|
354
|
+
ccw_target = ccw ? 1 : -1
|
355
|
+
|
356
|
+
coords = ring.start_point.coordinates
|
357
|
+
hedges = incident_edges[coords]
|
358
|
+
|
359
|
+
# find half-edges that are colinear to the start or end
|
360
|
+
# segment of the ring.
|
361
|
+
start_seg = Segment.new(ring.start_point, ring.point_n(1))
|
362
|
+
end_seg = Segment.new(ring.point_n(ring.num_points - 2), ring.end_point)
|
363
|
+
colinear_hedges = hedges.select do |he|
|
364
|
+
start_seg.side(he.destination).zero? || end_seg.side(he.destination).zero?
|
365
|
+
end
|
366
|
+
|
367
|
+
colinear_hedges.find do |hedge|
|
368
|
+
pts = []
|
369
|
+
hedge.and_connected do |he|
|
370
|
+
pts << he.origin
|
371
|
+
end
|
372
|
+
pts << hedge.origin
|
373
|
+
lr = parent_geometry.factory.line_string(pts)
|
374
|
+
Analysis.ring_direction(lr) == ccw_target
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|