class NSWTopo::GeoJSON::MultiPolygon

Public Instance Methods

area() click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 57
def area
  @coordinates.flatten(1).sum(&:signed_area)
end
buffer(*margins, **options) click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 140
def buffer(*margins, **options)
  nodes = Nodes.new @coordinates.flatten(1)
  margins.each do |margin|
    nodes.progress limit: -margin, **options.slice(:rounding_angle, :cutoff_angle)
  end
  interior_rings, exterior_rings = nodes.readout.partition(&:hole?)
  polygons, foo = exterior_rings.sort_by(&:signed_area).inject [[], interior_rings] do |(polygons, interior_rings), exterior_ring|
    claimed, unclaimed = interior_rings.partition do |interior_ring|
      interior_ring.first.within? exterior_ring
    end
    [polygons << [exterior_ring, *claimed], unclaimed]
  end
  MultiPolygon.new polygons.entries, @properties
end
centrelines(**options) click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 136
def centrelines(**options)
  centres(**options, interval: nil, lines: true)
end
centrepoints(interval:, **options) click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 132
def centrepoints(interval:, **options)
  centres(**options, interval: interval, lines: false)
end
centres(fraction: 0.5, min_width: nil, interval:, lines: true) click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 69
def centres(fraction: 0.5, min_width: nil, interval:, lines: true)
  neighbours = Hash.new { |neighbours, node| neighbours[node] = [] }
  samples, tails, node1 = {}, {}, nil

  Nodes.new(@coordinates.flatten(1)).progress(interval: interval) do |event, *args|
    case event
    when :nodes
      node0, node1 = *args
      neighbours[node0] << node1
      neighbours[node1] << node0
    when :interval
      travel, rings = *args
      samples[travel] = rings.flat_map do |ring|
        ring.sample_at interval
      end
    end
  end

  samples[node1.travel] = [node1.point.to_f]
  max_travel = neighbours.keys.map(&:travel).max
  min_travel = [fraction * max_travel, min_width && 0.5 * min_width].compact.max

  features = samples.select do |travel, points|
    travel > min_travel
  end.map do |travel, points|
    MultiPoint.new points, @properties
  end.reverse
  return features unless lines

  loop do
    break unless neighbours.reject do |node, (neighbour, *others)|
      others.any? || neighbours[neighbour].one?
    end.each do |node, (neighbour, *)|
      next if neighbours[neighbour].one?
      neighbours.delete node
      neighbours[neighbour].delete node
      nodes, length = tails.delete(node) || [[node], 0]
      candidate = [nodes << neighbour, length + [node.point, neighbour.point].distance]
      tails[neighbour] = [tails[neighbour], candidate].compact.max_by(&:last)
    end.any?
  end

  lengths, lines, candidates = Hash.new(0), Hash.new, tails.values
  while candidates.any?
    (*nodes, node), length = candidates.pop
    next if (neighbours[node] - nodes).each do |neighbour|
      candidates << [[*nodes, node, neighbour], length + [node.point, neighbour.point].distance]
    end.any?
    index = nodes.find(&:index).index
    tail_nodes, tail_length = tails[node] || [[node], 0]
    lengths[index], lines[index] = length + tail_length, nodes + tail_nodes.reverse if length + tail_length > lengths[index]
  end

  linestrings = lines.values.map do |nodes|
    nodes.chunk do |node|
      node.travel >= min_travel
    end.select(&:first).map(&:last).reject(&:one?).map do |nodes|
      nodes.map(&:point).to_f
    end
  end.flatten(1)
  features.prepend MultiLineString.new(linestrings, @properties)
end
centroids() click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 155
def centroids
  MultiPoint.new @coordinates.map(&:first).map(&:centroid), @properties
end
clip(hull) click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 6
def clip(hull)
  polys = @coordinates.inject([]) do |result, rings|
    lefthanded = rings.first.clockwise?
    interior, exterior = hull.zip(hull.perps).inject(rings) do |rings, (vertex, perp)|
      insides, neighbours, clipped = Hash[].compare_by_identity, Hash[].compare_by_identity, []
      rings.each do |points|
        points.map do |point|
          point.minus(vertex).dot(perp) >= 0
        end.segments.zip(points.segments).each do |inside, segment|
          insides[segment] = inside
          neighbours[segment] = [nil, nil]
        end.map(&:last).ring.each do |segment0, segment1|
          neighbours[segment1][0], neighbours[segment0][1] = segment0, segment1
        end
      end
      neighbours.select! do |segment, _|
        insides[segment].any?
      end
      insides.select do |segment, inside|
        inside.inject(&:^)
      end.each do |segment, inside|
        segment[inside[0] ? 1 : 0] = segment.along(vertex.minus(segment[0]).dot(perp) / segment.difference.dot(perp))
      end.sort_by do |segment, inside|
        segment[inside[0] ? 1 : 0].minus(vertex).cross(perp) * (lefthanded ? -1 : 1)
      end.map(&:first).each_slice(2) do |segment0, segment1|
        segment = [segment0[1], segment1[0]]
        neighbours[segment0][1] = neighbours[segment1][0] = segment
        neighbours[segment] = [segment0, segment1]
      end
      while neighbours.any?
        segment, * = neighbours.first
        clipped << []
        while neighbours.include? segment
          clipped.last << segment[0]
          *, segment = neighbours.delete(segment)
        end
        clipped.last << clipped.last.first
      end
      clipped
    end.partition(&:clockwise?).rotate(lefthanded ? 1 : 0)
    next result << exterior + interior if exterior.one?
    exterior.inject(result) do |result, exterior_ring|
      within, interior = interior.partition do |interior_ring|
        interior_ring.first.within? exterior_ring
      end
      result << [exterior_ring, *within]
    end
  end
  polys.none? ? nil : polys.one? ? Polygon.new(*polys, @properties) : MultiPolygon.new(polys, @properties)
end
samples(interval) click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 159
def samples(interval)
  points = @coordinates.flatten(1).flat_map do |ring|
    ring.sample_at interval
  end
  MultiPoint.new points, @properties
end
skeleton() click to toggle source
# File lib/nswtopo/gis/geojson/multi_polygon.rb, line 61
def skeleton
  segments = []
  Nodes.new(@coordinates.flatten(1)).progress do |event, node0, node1|
    segments << [node0.point, node1.point].to_f
  end
  MultiLineString.new segments, @properties
end