rgeo 2.3.1 → 3.0.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 +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,373 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
                        intersection_map[int.s1] ||= []
         
     | 
| 
      
 170 
     | 
    
         
            +
                        intersection_map[int.s1] << int.point
         
     | 
| 
      
 171 
     | 
    
         
            +
                      end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                      s2_intersects = int.point != int.s2.s && int.point != int.s2.e
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                      next unless s2_intersects
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                      intersection_map[int.s2] ||= []
         
     | 
| 
      
 178 
     | 
    
         
            +
                      intersection_map[int.s2] << int.point
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
                    intersection_map
         
     | 
| 
      
 181 
     | 
    
         
            +
                  end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                  def create_half_edges
         
     | 
| 
      
 184 
     | 
    
         
            +
                    @edges.each do |edge|
         
     | 
| 
      
 185 
     | 
    
         
            +
                      create_half_edge(edge)
         
     | 
| 
      
 186 
     | 
    
         
            +
                    end
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                  def create_half_edge(edge)
         
     | 
| 
      
 190 
     | 
    
         
            +
                    e1, e2 = HalfEdge.from_edge(edge)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    insert_half_edge(e1)
         
     | 
| 
      
 192 
     | 
    
         
            +
                    insert_half_edge(e2)
         
     | 
| 
      
 193 
     | 
    
         
            +
                  end
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                  def insert_half_edge(half_edge)
         
     | 
| 
      
 196 
     | 
    
         
            +
                    @incident_edges[half_edge.origin.coordinates] ||= []
         
     | 
| 
      
 197 
     | 
    
         
            +
                    @incident_edges[half_edge.origin.coordinates] << half_edge
         
     | 
| 
      
 198 
     | 
    
         
            +
                  end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                  # Links all half-edges where possible.
         
     | 
| 
      
 201 
     | 
    
         
            +
                  # Defines +next+ and +prev+ for every half-edge by rotating
         
     | 
| 
      
 202 
     | 
    
         
            +
                  # through all half-edges originating at a vertex.
         
     | 
| 
      
 203 
     | 
    
         
            +
                  #
         
     | 
| 
      
 204 
     | 
    
         
            +
                  # Assuming half-edges are sorted CCW, every sequential pair of
         
     | 
| 
      
 205 
     | 
    
         
            +
                  # half-edges (e1, e2) can be linked by saying e1.prev = e2.twin
         
     | 
| 
      
 206 
     | 
    
         
            +
                  # and e2.twin.next = e1.
         
     | 
| 
      
 207 
     | 
    
         
            +
                  def link_half_edges
         
     | 
| 
      
 208 
     | 
    
         
            +
                    incident_edges.each_value do |hedges|
         
     | 
| 
      
 209 
     | 
    
         
            +
                      hedges.sort!
         
     | 
| 
      
 210 
     | 
    
         
            +
                      [*hedges, hedges.first].each_cons(2) do |e1, e2|
         
     | 
| 
      
 211 
     | 
    
         
            +
                        e1.prev = e2.twin
         
     | 
| 
      
 212 
     | 
    
         
            +
                        e2.twin.next = e1
         
     | 
| 
      
 213 
     | 
    
         
            +
                      end
         
     | 
| 
      
 214 
     | 
    
         
            +
                    end
         
     | 
| 
      
 215 
     | 
    
         
            +
                  end
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                  # It is possible that intersections occur when new edges are added.
         
     | 
| 
      
 218 
     | 
    
         
            +
                  # This will split those edges into more half-edges while preserving
         
     | 
| 
      
 219 
     | 
    
         
            +
                  # the existing half-edges when possible since geometries may reference
         
     | 
| 
      
 220 
     | 
    
         
            +
                  # them.
         
     | 
| 
      
 221 
     | 
    
         
            +
                  def compute_split_edges(seg, ints)
         
     | 
| 
      
 222 
     | 
    
         
            +
                    points = ints.uniq.sort do |a, b|
         
     | 
| 
      
 223 
     | 
    
         
            +
                      a.distance(seg.s) <=> b.distance(seg.s)
         
     | 
| 
      
 224 
     | 
    
         
            +
                    end
         
     | 
| 
      
 225 
     | 
    
         
            +
                    points = [seg.s] + points + [seg.e]
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                    he_start = incident_edges[points.first.coordinates].find { |he| he.destination == points.last }
         
     | 
| 
      
 228 
     | 
    
         
            +
                    he_end = incident_edges[points.last.coordinates].find { |he| he.destination == points.first }
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
                    points.each_cons(2) do |s, e|
         
     | 
| 
      
 231 
     | 
    
         
            +
                      edge = Segment.new(s, e)
         
     | 
| 
      
 232 
     | 
    
         
            +
                      if s == he_start.origin
         
     | 
| 
      
 233 
     | 
    
         
            +
                        he = HalfEdge.new(e)
         
     | 
| 
      
 234 
     | 
    
         
            +
                        he_start.twin = he
         
     | 
| 
      
 235 
     | 
    
         
            +
                        he.twin = he_start
         
     | 
| 
      
 236 
     | 
    
         
            +
                        insert_half_edge(he)
         
     | 
| 
      
 237 
     | 
    
         
            +
                      elsif e == he_end.origin
         
     | 
| 
      
 238 
     | 
    
         
            +
                        he = HalfEdge.new(s)
         
     | 
| 
      
 239 
     | 
    
         
            +
                        he_end.twin = he
         
     | 
| 
      
 240 
     | 
    
         
            +
                        he.twin = he_end
         
     | 
| 
      
 241 
     | 
    
         
            +
                        insert_half_edge(he)
         
     | 
| 
      
 242 
     | 
    
         
            +
                      else
         
     | 
| 
      
 243 
     | 
    
         
            +
                        create_half_edge(edge)
         
     | 
| 
      
 244 
     | 
    
         
            +
                      end
         
     | 
| 
      
 245 
     | 
    
         
            +
                      @edges << edge
         
     | 
| 
      
 246 
     | 
    
         
            +
                    end
         
     | 
| 
      
 247 
     | 
    
         
            +
                    @edges.delete(seg)
         
     | 
| 
      
 248 
     | 
    
         
            +
                  end
         
     | 
| 
      
 249 
     | 
    
         
            +
                end
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                # GeometryGraph is a PlanarGraph that is built by adding
         
     | 
| 
      
 252 
     | 
    
         
            +
                # geometries instead of edges. The GeometryGraph will
         
     | 
| 
      
 253 
     | 
    
         
            +
                # hold a reference to an arbitrary HalfEdge on the
         
     | 
| 
      
 254 
     | 
    
         
            +
                # interior of the geometry for every boundary in the geometry.
         
     | 
| 
      
 255 
     | 
    
         
            +
                # For example, a polygon will have a reference to a HalfEdge for its
         
     | 
| 
      
 256 
     | 
    
         
            +
                # exterior shell and one for every hole.
         
     | 
| 
      
 257 
     | 
    
         
            +
                class GeometryGraph < PlanarGraph
         
     | 
| 
      
 258 
     | 
    
         
            +
                  # GeomEdge will be used to store the references to the HalfEdges
         
     | 
| 
      
 259 
     | 
    
         
            +
                  GeomEdge = Struct.new(:exterior_edge, :interior_edges)
         
     | 
| 
      
 260 
     | 
    
         
            +
             
     | 
| 
      
 261 
     | 
    
         
            +
                  def initialize(geom)
         
     | 
| 
      
 262 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 263 
     | 
    
         
            +
                    @parent_geometry = geom
         
     | 
| 
      
 264 
     | 
    
         
            +
                    @geom_edges = []
         
     | 
| 
      
 265 
     | 
    
         
            +
                    add_geometry(geom)
         
     | 
| 
      
 266 
     | 
    
         
            +
                  end
         
     | 
| 
      
 267 
     | 
    
         
            +
                  attr_reader :parent_geometry, :geom_edges
         
     | 
| 
      
 268 
     | 
    
         
            +
             
     | 
| 
      
 269 
     | 
    
         
            +
                  private
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
                  # Adds a geometry to the graph and finds its
         
     | 
| 
      
 272 
     | 
    
         
            +
                  # reference HalfEdge(s).
         
     | 
| 
      
 273 
     | 
    
         
            +
                  #
         
     | 
| 
      
 274 
     | 
    
         
            +
                  # @param geom [RGeo::Feature::Instance]
         
     | 
| 
      
 275 
     | 
    
         
            +
                  def add_geometry(geom)
         
     | 
| 
      
 276 
     | 
    
         
            +
                    case geom
         
     | 
| 
      
 277 
     | 
    
         
            +
                    when Feature::Point
         
     | 
| 
      
 278 
     | 
    
         
            +
                      # Can't handle points yet, so just add an empty entry for them
         
     | 
| 
      
 279 
     | 
    
         
            +
                      @geom_edges << GeomEdge.new
         
     | 
| 
      
 280 
     | 
    
         
            +
                    when Feature::LineString, Feature::LinearRing
         
     | 
| 
      
 281 
     | 
    
         
            +
                      add_line_string(geom)
         
     | 
| 
      
 282 
     | 
    
         
            +
                    when Feature::Polygon
         
     | 
| 
      
 283 
     | 
    
         
            +
                      add_polygon(geom)
         
     | 
| 
      
 284 
     | 
    
         
            +
                    when Feature::GeometryCollection
         
     | 
| 
      
 285 
     | 
    
         
            +
                      add_collection(geom)
         
     | 
| 
      
 286 
     | 
    
         
            +
                    else
         
     | 
| 
      
 287 
     | 
    
         
            +
                      raise Error::RGeoError, "Invalid Geometry Type: #{geom.class}"
         
     | 
| 
      
 288 
     | 
    
         
            +
                    end
         
     | 
| 
      
 289 
     | 
    
         
            +
                  end
         
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
      
 291 
     | 
    
         
            +
                  # Adds a LineString or LinearRing
         
     | 
| 
      
 292 
     | 
    
         
            +
                  # to the graph.
         
     | 
| 
      
 293 
     | 
    
         
            +
                  #
         
     | 
| 
      
 294 
     | 
    
         
            +
                  # @param geom [RGeo::Feature::LineString]
         
     | 
| 
      
 295 
     | 
    
         
            +
                  def add_line_string(geom)
         
     | 
| 
      
 296 
     | 
    
         
            +
                    add_edges(geom.segments)
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                    hedge = @incident_edges[geom.start_point.coordinates].first unless geom.empty?
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
                    @geom_edges << GeomEdge.new(hedge, nil)
         
     | 
| 
      
 301 
     | 
    
         
            +
                  end
         
     | 
| 
      
 302 
     | 
    
         
            +
             
     | 
| 
      
 303 
     | 
    
         
            +
                  # Adds a Polygon to the graph.
         
     | 
| 
      
 304 
     | 
    
         
            +
                  #
         
     | 
| 
      
 305 
     | 
    
         
            +
                  # Will add each shell seperately and find a CCW half-edge
         
     | 
| 
      
 306 
     | 
    
         
            +
                  # for the exterior ring and a CW half-edge for interior rings
         
     | 
| 
      
 307 
     | 
    
         
            +
                  # since these are designated as being on the interior of the polygon.
         
     | 
| 
      
 308 
     | 
    
         
            +
                  #
         
     | 
| 
      
 309 
     | 
    
         
            +
                  # It is possible that the half-edge will be nil if a suitable one cannot
         
     | 
| 
      
 310 
     | 
    
         
            +
                  # be found. This indicates the polygon is likely invalid.
         
     | 
| 
      
 311 
     | 
    
         
            +
                  #
         
     | 
| 
      
 312 
     | 
    
         
            +
                  # @param geom [RGeo::Feature::Polygon]
         
     | 
| 
      
 313 
     | 
    
         
            +
                  def add_polygon(geom)
         
     | 
| 
      
 314 
     | 
    
         
            +
                    exterior = geom.exterior_ring
         
     | 
| 
      
 315 
     | 
    
         
            +
                    add_edges(exterior.segments)
         
     | 
| 
      
 316 
     | 
    
         
            +
             
     | 
| 
      
 317 
     | 
    
         
            +
                    hedge = find_hedge(exterior)
         
     | 
| 
      
 318 
     | 
    
         
            +
             
     | 
| 
      
 319 
     | 
    
         
            +
                    interior_hedges = []
         
     | 
| 
      
 320 
     | 
    
         
            +
                    geom.interior_rings.each do |interior|
         
     | 
| 
      
 321 
     | 
    
         
            +
                      add_edges(interior.segments)
         
     | 
| 
      
 322 
     | 
    
         
            +
                      interior_hedges << find_hedge(interior, ccw: false)
         
     | 
| 
      
 323 
     | 
    
         
            +
                    end
         
     | 
| 
      
 324 
     | 
    
         
            +
             
     | 
| 
      
 325 
     | 
    
         
            +
                    @geom_edges << GeomEdge.new(hedge, interior_hedges)
         
     | 
| 
      
 326 
     | 
    
         
            +
                  end
         
     | 
| 
      
 327 
     | 
    
         
            +
             
     | 
| 
      
 328 
     | 
    
         
            +
                  # Adds a GeometryCollection to the graph.
         
     | 
| 
      
 329 
     | 
    
         
            +
                  #
         
     | 
| 
      
 330 
     | 
    
         
            +
                  # @param col [RGeo::Feature::GeometryCollection]
         
     | 
| 
      
 331 
     | 
    
         
            +
                  def add_collection(col)
         
     | 
| 
      
 332 
     | 
    
         
            +
                    col.each do |geom|
         
     | 
| 
      
 333 
     | 
    
         
            +
                      add_geometry(geom)
         
     | 
| 
      
 334 
     | 
    
         
            +
                    end
         
     | 
| 
      
 335 
     | 
    
         
            +
                  end
         
     | 
| 
      
 336 
     | 
    
         
            +
             
     | 
| 
      
 337 
     | 
    
         
            +
                  # Finds a Half-Edge that is part of a CCW or CW rotation
         
     | 
| 
      
 338 
     | 
    
         
            +
                  # from the input ring. Returns nil if none found.
         
     | 
| 
      
 339 
     | 
    
         
            +
                  #
         
     | 
| 
      
 340 
     | 
    
         
            +
                  # Will only consider half-edges that are colinear with
         
     | 
| 
      
 341 
     | 
    
         
            +
                  # the first or last segments of the ring.
         
     | 
| 
      
 342 
     | 
    
         
            +
                  #
         
     | 
| 
      
 343 
     | 
    
         
            +
                  # @param ring [RGeo::Feature::LinearRing]
         
     | 
| 
      
 344 
     | 
    
         
            +
                  # @param ccw [Boolean] true for CCW, false for CW
         
     | 
| 
      
 345 
     | 
    
         
            +
                  # @return [HalfEdge, nil]
         
     | 
| 
      
 346 
     | 
    
         
            +
                  def find_hedge(ring, ccw: true)
         
     | 
| 
      
 347 
     | 
    
         
            +
                    return nil if ring.num_points == 0
         
     | 
| 
      
 348 
     | 
    
         
            +
                    ccw_target = ccw ? 1 : -1
         
     | 
| 
      
 349 
     | 
    
         
            +
             
     | 
| 
      
 350 
     | 
    
         
            +
                    coords = ring.start_point.coordinates
         
     | 
| 
      
 351 
     | 
    
         
            +
                    hedges = incident_edges[coords]
         
     | 
| 
      
 352 
     | 
    
         
            +
             
     | 
| 
      
 353 
     | 
    
         
            +
                    # find half-edges that are colinear to the start or end
         
     | 
| 
      
 354 
     | 
    
         
            +
                    # segment of the ring.
         
     | 
| 
      
 355 
     | 
    
         
            +
                    start_seg = Segment.new(ring.start_point, ring.point_n(1))
         
     | 
| 
      
 356 
     | 
    
         
            +
                    end_seg = Segment.new(ring.point_n(ring.num_points - 2), ring.end_point)
         
     | 
| 
      
 357 
     | 
    
         
            +
                    colinear_hedges = hedges.select do |he|
         
     | 
| 
      
 358 
     | 
    
         
            +
                      start_seg.side(he.destination) == 0 || end_seg.side(he.destination) == 0
         
     | 
| 
      
 359 
     | 
    
         
            +
                    end
         
     | 
| 
      
 360 
     | 
    
         
            +
             
     | 
| 
      
 361 
     | 
    
         
            +
                    colinear_hedges.find do |hedge|
         
     | 
| 
      
 362 
     | 
    
         
            +
                      pts = []
         
     | 
| 
      
 363 
     | 
    
         
            +
                      hedge.and_connected do |he|
         
     | 
| 
      
 364 
     | 
    
         
            +
                        pts << he.origin
         
     | 
| 
      
 365 
     | 
    
         
            +
                      end
         
     | 
| 
      
 366 
     | 
    
         
            +
                      pts << hedge.origin
         
     | 
| 
      
 367 
     | 
    
         
            +
                      lr = parent_geometry.factory.line_string(pts)
         
     | 
| 
      
 368 
     | 
    
         
            +
                      Analysis.ring_direction(lr) == ccw_target
         
     | 
| 
      
 369 
     | 
    
         
            +
                    end
         
     | 
| 
      
 370 
     | 
    
         
            +
                  end
         
     | 
| 
      
 371 
     | 
    
         
            +
                end
         
     | 
| 
      
 372 
     | 
    
         
            +
              end
         
     | 
| 
      
 373 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,147 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # -----------------------------------------------------------------------------
         
     | 
| 
      
 4 
     | 
    
         
            +
            #
         
     | 
| 
      
 5 
     | 
    
         
            +
            # Simple Sweepline Intersector Class
         
     | 
| 
      
 6 
     | 
    
         
            +
            #
         
     | 
| 
      
 7 
     | 
    
         
            +
            # -----------------------------------------------------------------------------
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module RGeo
         
     | 
| 
      
 10 
     | 
    
         
            +
              module Cartesian
         
     | 
| 
      
 11 
     | 
    
         
            +
                # Implements a Sweepline intersector to find all intersections
         
     | 
| 
      
 12 
     | 
    
         
            +
                # in a group of segments. The idea is to use a horizontal line starting
         
     | 
| 
      
 13 
     | 
    
         
            +
                # at y = +Infinity that sweeps down to y = -Infinity and every time it hits
         
     | 
| 
      
 14 
     | 
    
         
            +
                # a new line, it will check if it intersects with any of the segments
         
     | 
| 
      
 15 
     | 
    
         
            +
                # the line currently intersects at that y value.
         
     | 
| 
      
 16 
     | 
    
         
            +
                # This is a more simplistic implementation that uses an array to hold
         
     | 
| 
      
 17 
     | 
    
         
            +
                # observed segments instead of a sorted BST, so performance may be significantly
         
     | 
| 
      
 18 
     | 
    
         
            +
                # worse in the case of lots of segments overlapping in y-ranges.
         
     | 
| 
      
 19 
     | 
    
         
            +
                class SweeplineIntersector
         
     | 
| 
      
 20 
     | 
    
         
            +
                  Event = Struct.new(:point, :segment, :is_start)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  Intersection = Struct.new(:point, :s1, :s2)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def initialize(segments)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @segments = segments
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  attr_reader :segments
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  # Returns the "proper" intersections from the list of segments.
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # This will only return intersections that are not the start/end or
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # end/start of the 2 segments. This could be useful for finding intersections
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # in a ring for example, because knowing that segments are connected in a linestring
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # is not always helpful, but those are reported by default.
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # Note: This is not the true definition of a proper intersection. A
         
     | 
| 
      
 36 
     | 
    
         
            +
                  # truly proper intersection does not include colinear intersections and
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # the intersection must lie in the interior of both segments.
         
     | 
| 
      
 38 
     | 
    
         
            +
                  #
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # @return [Array<RGeo::Cartesian::SweeplineIntersector::Intersection>]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  def proper_intersections
         
     | 
| 
      
 41 
     | 
    
         
            +
                    return @proper_intersections if defined?(@proper_intersections)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    @proper_intersections = []
         
     | 
| 
      
 44 
     | 
    
         
            +
                    intersections.each do |intersection|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      s1 = intersection.s1
         
     | 
| 
      
 46 
     | 
    
         
            +
                      s2 = intersection.s2
         
     | 
| 
      
 47 
     | 
    
         
            +
                      pt = intersection.point
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                      @proper_intersections << intersection unless (pt == s1.s && pt == s2.e) || (pt == s1.e && pt == s2.s)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                    @proper_intersections
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  # Computes the intersections of the input segments.
         
     | 
| 
      
 55 
     | 
    
         
            +
                  #
         
     | 
| 
      
 56 
     | 
    
         
            +
                  # Creates an event queue from the +events+ and adds segments to the
         
     | 
| 
      
 57 
     | 
    
         
            +
                  # +observed_segments+ array while their ending event has not been popped
         
     | 
| 
      
 58 
     | 
    
         
            +
                  # from the queue.
         
     | 
| 
      
 59 
     | 
    
         
            +
                  #
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # Compares the new segment from the +is_start+ event to each observed segment
         
     | 
| 
      
 61 
     | 
    
         
            +
                  # then adds it to +observed_segments+. Records any intersections in to the
         
     | 
| 
      
 62 
     | 
    
         
            +
                  # returned array.
         
     | 
| 
      
 63 
     | 
    
         
            +
                  #
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # @return [Array<RGeo::Cartesian::SweeplineIntersector::Intersection>]
         
     | 
| 
      
 65 
     | 
    
         
            +
                  def intersections
         
     | 
| 
      
 66 
     | 
    
         
            +
                    return @intersections if defined?(@intersections)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    @intersections = []
         
     | 
| 
      
 69 
     | 
    
         
            +
                    observed_segments = Set.new
         
     | 
| 
      
 70 
     | 
    
         
            +
                    events.each do |e|
         
     | 
| 
      
 71 
     | 
    
         
            +
                      seg = e.segment
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                      if e.is_start
         
     | 
| 
      
 74 
     | 
    
         
            +
                        observed_segments.each do |oseg|
         
     | 
| 
      
 75 
     | 
    
         
            +
                          int_pt = seg.segment_intersection(oseg)
         
     | 
| 
      
 76 
     | 
    
         
            +
                          if int_pt
         
     | 
| 
      
 77 
     | 
    
         
            +
                            intersect = Intersection.new(int_pt, seg, oseg)
         
     | 
| 
      
 78 
     | 
    
         
            +
                            @intersections << intersect
         
     | 
| 
      
 79 
     | 
    
         
            +
                          end
         
     | 
| 
      
 80 
     | 
    
         
            +
                        end
         
     | 
| 
      
 81 
     | 
    
         
            +
                        observed_segments << seg
         
     | 
| 
      
 82 
     | 
    
         
            +
                      else
         
     | 
| 
      
 83 
     | 
    
         
            +
                        observed_segments.delete(seg)
         
     | 
| 
      
 84 
     | 
    
         
            +
                      end
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
                    @intersections
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  # Returns an ordered array of events from the input segments. Events
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # are the start and endpoints of each segment with an is_start tag to
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # indicate if this is the starting or ending event for that segment.
         
     | 
| 
      
 92 
     | 
    
         
            +
                  #
         
     | 
| 
      
 93 
     | 
    
         
            +
                  # Ordering is done by greatest-y -> smallest-x -> is_start = true.
         
     | 
| 
      
 94 
     | 
    
         
            +
                  #
         
     | 
| 
      
 95 
     | 
    
         
            +
                  # @return [Array]
         
     | 
| 
      
 96 
     | 
    
         
            +
                  def events
         
     | 
| 
      
 97 
     | 
    
         
            +
                    return @events if defined?(@events)
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                    @events = []
         
     | 
| 
      
 100 
     | 
    
         
            +
                    segments.each do |segment|
         
     | 
| 
      
 101 
     | 
    
         
            +
                      event_pair = create_event_pair(segment)
         
     | 
| 
      
 102 
     | 
    
         
            +
                      @events.concat(event_pair)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                    @events.sort! do |a, b|
         
     | 
| 
      
 106 
     | 
    
         
            +
                      if a.point == b.point
         
     | 
| 
      
 107 
     | 
    
         
            +
                        if a.is_start
         
     | 
| 
      
 108 
     | 
    
         
            +
                          -1
         
     | 
| 
      
 109 
     | 
    
         
            +
                        else
         
     | 
| 
      
 110 
     | 
    
         
            +
                          1
         
     | 
| 
      
 111 
     | 
    
         
            +
                        end
         
     | 
| 
      
 112 
     | 
    
         
            +
                      elsif a.point.y == b.point.y
         
     | 
| 
      
 113 
     | 
    
         
            +
                        a.point.x <=> b.point.x
         
     | 
| 
      
 114 
     | 
    
         
            +
                      else
         
     | 
| 
      
 115 
     | 
    
         
            +
                        b.point.y <=> a.point.y
         
     | 
| 
      
 116 
     | 
    
         
            +
                      end
         
     | 
| 
      
 117 
     | 
    
         
            +
                    end
         
     | 
| 
      
 118 
     | 
    
         
            +
                    @events
         
     | 
| 
      
 119 
     | 
    
         
            +
                  end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                  private
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                  # Creates a pair of events from a segment
         
     | 
| 
      
 124 
     | 
    
         
            +
                  #
         
     | 
| 
      
 125 
     | 
    
         
            +
                  # @param segment [Segment]
         
     | 
| 
      
 126 
     | 
    
         
            +
                  #
         
     | 
| 
      
 127 
     | 
    
         
            +
                  # @return [Array]
         
     | 
| 
      
 128 
     | 
    
         
            +
                  def create_event_pair(segment)
         
     | 
| 
      
 129 
     | 
    
         
            +
                    s = segment.s
         
     | 
| 
      
 130 
     | 
    
         
            +
                    e = segment.e
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                    s_event = Event.new(s, segment)
         
     | 
| 
      
 133 
     | 
    
         
            +
                    e_event = Event.new(e, segment)
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                    if s.y > e.y || (s.y == e.y && s.x < e.x)
         
     | 
| 
      
 136 
     | 
    
         
            +
                      s_event.is_start = true
         
     | 
| 
      
 137 
     | 
    
         
            +
                      e_event.is_start = false
         
     | 
| 
      
 138 
     | 
    
         
            +
                    else
         
     | 
| 
      
 139 
     | 
    
         
            +
                      s_event.is_start = false
         
     | 
| 
      
 140 
     | 
    
         
            +
                      e_event.is_start = true
         
     | 
| 
      
 141 
     | 
    
         
            +
                    end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                    [s_event, e_event]
         
     | 
| 
      
 144 
     | 
    
         
            +
                  end
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
              end
         
     | 
| 
      
 147 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,69 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module RGeo
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Cartesian
         
     | 
| 
      
 5 
     | 
    
         
            +
                module ValidOp
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include ImplHelper::ValidOp
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def validity_helper
         
     | 
| 
      
 9 
     | 
    
         
            +
                    ValidOpHelpers
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                module ValidOpHelpers
         
     | 
| 
      
 14 
     | 
    
         
            +
                  include ImplHelper::ValidOpHelpers
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  module_function(*ImplHelper::ValidOpHelpers.singleton_methods) # rubocop:disable Style/AccessModifierDeclarations
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  module_function
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  # Checks that there are no invalid intersections between the components
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # of a polygon.
         
     | 
| 
      
 22 
     | 
    
         
            +
                  #
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # @param [RGeo::Feature::Polygon] poly
         
     | 
| 
      
 24 
     | 
    
         
            +
                  #
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # @return [String] invalid_reason
         
     | 
| 
      
 26 
     | 
    
         
            +
                  def check_consistent_area(poly)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    # Get set of unique coords
         
     | 
| 
      
 28 
     | 
    
         
            +
                    pts = poly.exterior_ring.coordinates.to_set
         
     | 
| 
      
 29 
     | 
    
         
            +
                    poly.interior_rings.each do |ring|
         
     | 
| 
      
 30 
     | 
    
         
            +
                      pts += ring.coordinates
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                    num_points = pts.size
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    # if additional nodes were added, there must be an intersection
         
     | 
| 
      
 35 
     | 
    
         
            +
                    # through a boundary.
         
     | 
| 
      
 36 
     | 
    
         
            +
                    return Error::SELF_INTERSECTION if poly.send(:graph).incident_edges.size > num_points
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    rings = [poly.exterior_ring] + poly.interior_rings
         
     | 
| 
      
 39 
     | 
    
         
            +
                    return Error::SELF_INTERSECTION if rings.uniq.size != rings.size
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  # Checks that the interior of a polygon is connected.
         
     | 
| 
      
 45 
     | 
    
         
            +
                  #
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # Process to do this is to walk around an interior cycle of the
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # exterior shell in the polygon's geometry graph. It will keep track
         
     | 
| 
      
 48 
     | 
    
         
            +
                  # of all the nodes it visited and if that set is a superset of the
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # coordinates in the exterior_ring, the interior is connected, otherwise
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # it is split somewhere.
         
     | 
| 
      
 51 
     | 
    
         
            +
                  #
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # @param [RGeo::Feature::Polygon] poly
         
     | 
| 
      
 53 
     | 
    
         
            +
                  #
         
     | 
| 
      
 54 
     | 
    
         
            +
                  # @return [String] invalid_reason
         
     | 
| 
      
 55 
     | 
    
         
            +
                  def check_connected_interiors(poly)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    exterior_coords = poly.exterior_ring.coordinates.to_set
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    visited = Set.new
         
     | 
| 
      
 59 
     | 
    
         
            +
                    poly.send(:graph).geom_edges.first.exterior_edge.and_connected do |hedge|
         
     | 
| 
      
 60 
     | 
    
         
            +
                      visited << hedge.origin.coordinates
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                    return Error::DISCONNECTED_INTERIOR unless exterior_coords.subset?(visited)
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/rgeo/cartesian.rb
    CHANGED
    
    | 
         @@ -8,8 +8,11 @@ 
     | 
|
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
            require_relative "cartesian/calculations"
         
     | 
| 
       10 
10 
     | 
    
         
             
            require_relative "cartesian/feature_methods"
         
     | 
| 
      
 11 
     | 
    
         
            +
            require_relative "cartesian/valid_op"
         
     | 
| 
       11 
12 
     | 
    
         
             
            require_relative "cartesian/feature_classes"
         
     | 
| 
       12 
13 
     | 
    
         
             
            require_relative "cartesian/factory"
         
     | 
| 
       13 
14 
     | 
    
         
             
            require_relative "cartesian/interface"
         
     | 
| 
       14 
15 
     | 
    
         
             
            require_relative "cartesian/bounding_box"
         
     | 
| 
       15 
16 
     | 
    
         
             
            require_relative "cartesian/analysis"
         
     | 
| 
      
 17 
     | 
    
         
            +
            require_relative "cartesian/sweepline_intersector"
         
     | 
| 
      
 18 
     | 
    
         
            +
            require_relative "cartesian/planar_graph"
         
     |