module GeoGraf class Polygon def initialize(coordinates, factory) @rgeo_polygons = [create_polygon(coordinates, factory)] create_shadow_polygon_if_required(coordinates, factory) end def intersection_area(other) intersection = intersection(other) if intersection.respond_to?(:area) intersection.area elsif intersection.respond_to?(:map) sum_of_areas_in(intersection) else 0.0 end end def area rgeo_polygons.first.area end protected attr_reader :rgeo_polygons private def intersection(other) intersection = nil # If you wonder why for a seemingly simple intersection of two # polygons we are actually intersecting multiple rgeo_polygons # with other's rgeo_polygons, check out the comment above # #create_shadow_polygon_if_required. Peace. rgeo_polygons.each do |polygon| other.rgeo_polygons.each do |other_polygon| intersection = polygon.intersection(other_polygon) return intersection if intersection && !intersection.is_empty? end end intersection end def create_polygon(coordinates, factory, longitude_offset: 0) points = coordinates.map { |c| factory.point(c[1] + longitude_offset, c[0]) } ring = factory.linear_ring(points) factory.polygon(ring) end def sum_of_areas_in(collection) collection .select { |i| i.respond_to?(:area) } .map(&:area) .inject(0, :+) end # Ah, the shadow polygon... We use it because RGeo is kind of retarded # and doesn't deal with situations in which shapes wrap around # the 180th meridian correctly. Therefore we create a fake shape with # an offset of either 360 or -360 degrees to ensure that we catch # the intersection if there is one. And, the name is awesome, right? def create_shadow_polygon_if_required(coordinates, factory) if line_180(factory).intersects?(rgeo_polygons.first) rgeo_polygons << create_polygon(coordinates, factory, longitude_offset: -360) elsif line_minus_180(factory).intersects?(rgeo_polygons.first) rgeo_polygons << create_polygon(coordinates, factory, longitude_offset: 360) end end def line_180(factory) factory.line(factory.point(180, 90), factory.point(180, -90)) end def line_minus_180(factory) factory.line(factory.point(-180, 90), factory.point(-180, -90)) end end end