Class: NSWTopo::Labels::ConvexHulls

Inherits:
GeoJSON::MultiLineString show all
Defined in:
lib/nswtopo/layer/labels/convex_hulls.rb

Direct Known Subclasses

Label

Constant Summary

Constants included from StraightSkeleton

StraightSkeleton::DEFAULT_ROUNDING_ANGLE

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from GeoJSON::MultiLineString

#buffer, #dissolve_points, #freeze!, #nodes, #offset, #path_length, #samples, #smooth, #subdivide, #to_multipolygon, #to_polygon, #trim

Constructor Details

#initialize(feature, buffer, &block) ⇒ ConvexHulls

Returns a new instance of ConvexHulls.



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/nswtopo/layer/labels/convex_hulls.rb', line 4

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

Class Method Details

.overlap?(ring0, ring1, buffer) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/nswtopo/layer/labels/convex_hulls.rb', line 44

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

Instance Method Details

#each(&block) ⇒ Object



35
36
37
38
39
40
41
42
# File 'lib/nswtopo/layer/labels/convex_hulls.rb', line 35

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