class NSWTopo::Labels::ConvexHulls
def self.overlap?(ring0, ring1, buffer)
def self.overlap?(ring0, ring1, buffer) # implements Gilbert–Johnson–Keerthi simplex = [ring0.first - ring1.first] perp = simplex[0].perp loop do return true unless case when simplex.one? then simplex[0].norm when simplex.inject(&:-).dot(simplex[1]) > 0 then simplex[1].norm when simplex.inject(&:-).dot(simplex[0]) < 0 then simplex[0].norm else simplex.inject(&:cross).abs / simplex.inject(&:-).norm end > buffer max = ring0.max_by { |point| perp.cross point } min = ring1.min_by { |point| perp.cross point } support = max - min return false unless (simplex[0] - support).cross(perp) > 0 rays = simplex.map { |point| point - support } case simplex.length when 1 case when rays[0].dot(support) > 0 simplex, perp = [support], support.perp when rays[0].cross(support) < 0 simplex, perp = [support, *simplex], rays[0] else simplex, perp = [*simplex, support], -rays[0] end when 2 case when rays[0].cross(support) > 0 && rays[0].dot(support) < 0 simplex, perp = [simplex[0], support], -rays[0] when rays[1].cross(support) < 0 && rays[1].dot(support) < 0 simplex, perp = [support, simplex[1]], rays[1] when rays[0].cross(support) <= 0 && rays[1].cross(support) >= 0 return true else simplex, perp = [support], support.perp end end end end
def each(&block)
def each(&block) enum = Enumerator.new do |yielder| @coordinates.each do |coordinates| yielder << ConvexHull.new(self, coordinates) end end block_given? ? enum.each(&block) : enum end
def initialize(feature, buffer, &block)
def initialize(feature, buffer, &block) coordinates = case feature when GeoJSON::Polygon then feature.rings when GeoJSON::MultiPolygon then feature.rings else feature end.explode.flat_map do |feature| case feature when GeoJSON::Point # a point feature barrier x, y = *feature [[Vector[x-buffer, y-buffer], Vector[x+buffer, y-buffer], Vector[x+buffer, y+buffer], Vector[x-buffer, y+buffer]]] when GeoJSON::LineString # a linestring label to be broken down into segment hulls offsets = feature.each_cons(2).map do |p0, p1| (p1 - p0).perp.normalised * buffer end corners = offsets.then do |offsets| feature.closed? ? [offsets.last, *offsets, offsets.first] : [offsets.first, *offsets, offsets.last] end.each_cons(2).map do |o01, o12| next if o12.cross(o01) == 0 (o01 + o12).normalised * buffer * (o12.cross(o01) <=> 0) end.each_cons(2) feature.each_cons(2).zip(corners, offsets).map do |(p0, p1), (c0, c1), offset| if c0 then [p0 + offset, p0 + c0, p0 - offset] else [p0 + offset, p0 - offset] end + if c1 then [p1 - offset, p1 + c1, p1 + offset] else [p1 - offset, p1 + offset] end end end end super coordinates, &block end