diff --git a/lib/turf/boolean_concave.rb b/lib/turf/boolean_concave.rb index 5e24f3b..ad9e479 100644 --- a/lib/turf/boolean_concave.rb +++ b/lib/turf/boolean_concave.rb @@ -2,7 +2,39 @@ # :nodoc: module Turf - def boolean_concave(*args) - raise NotImplementedError + # frozen_string_literal: true + + # Takes a polygon and returns true or false as to whether it is concave or not. + # + # @function + # @param [Feature, Polygon] polygon to be evaluated + # @return [Boolean] true/false + # @example + # convex_polygon = polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]) + # + # boolean_concave(convex_polygon) + # # => false + def boolean_concave(polygon) + coords = get_geom(polygon)[:coordinates] + return false if coords[0].length <= 4 + + sign = nil + n = coords[0].length - 1 + + (0...n).each do |i| + dx1 = coords[0][(i + 2) % n][0] - coords[0][(i + 1) % n][0] + dy1 = coords[0][(i + 2) % n][1] - coords[0][(i + 1) % n][1] + dx2 = coords[0][i][0] - coords[0][(i + 1) % n][0] + dy2 = coords[0][i][1] - coords[0][(i + 1) % n][1] + zcrossproduct = (dx1 * dy2) - (dy1 * dx2) + + if i.zero? + sign = zcrossproduct.positive? + elsif sign != zcrossproduct.positive? + return true + end + end + + false end end diff --git a/test/turf_boolean_concave_test.rb b/test/turf_boolean_concave_test.rb new file mode 100644 index 0000000..6ceff50 --- /dev/null +++ b/test/turf_boolean_concave_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "test_helper" + +class TurfBooleanConcaveTest < Minitest::Test + def test_is_concave_fixtures + # True Fixtures + Dir.glob(File.join(__dir__, "boolean_concave", "true", "*.geojson")).each do |filepath| + name = File.basename(filepath, ".geojson") + geojson = JSON.parse(File.read(filepath)) + feature = geojson[:features][0] + assert(Turf.boolean_concave(feature), "[true] #{name}") + end + + # False Fixtures + Dir.glob(File.join(__dir__, "boolean_concave", "false", "*.geojson")).each do |filepath| + name = File.basename(filepath, ".geojson") + geojson = JSON.parse(File.read(filepath)) + feature = geojson[:features][0] + refute(Turf.boolean_concave(feature), "[false] #{name}") + end + end + + def test_is_concave_geometry_types + poly = Turf.polygon([ + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0], + ], + ]) + + assert_equal(false, Turf.boolean_concave(poly), "Feature") + assert_equal(false, Turf.boolean_concave(poly[:geometry]), "Geometry Object") + end +end diff --git a/test/turf_concave/false/3vertices.geojson b/test/turf_concave/false/3vertices.geojson new file mode 100644 index 0000000..3537ffd --- /dev/null +++ b/test/turf_concave/false/3vertices.geojson @@ -0,0 +1,20 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], + [1, 1], + [1, 0], + [0, 0] + ] + ] + } + } + ] +} diff --git a/test/turf_concave/false/diamond.geojson b/test/turf_concave/false/diamond.geojson new file mode 100644 index 0000000..6cc6619 --- /dev/null +++ b/test/turf_concave/false/diamond.geojson @@ -0,0 +1,22 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0.69488525390625, 1.1754546449158993], + [0.263671875, 0.9118267425981088], + [0.56854248046875, 0.4312093081601601], + [0.96954345703125, 0.771766211876697], + [0.83221435546875, 1.016182073033441], + [0.69488525390625, 1.1754546449158993] + ] + ] + } + } + ] +} diff --git a/test/turf_concave/false/square.geojson b/test/turf_concave/false/square.geojson new file mode 100644 index 0000000..e86e427 --- /dev/null +++ b/test/turf_concave/false/square.geojson @@ -0,0 +1,21 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ] + ] + } + } + ] +} diff --git a/test/turf_concave/true/polygon.geojson b/test/turf_concave/true/polygon.geojson new file mode 100644 index 0000000..588bb79 --- /dev/null +++ b/test/turf_concave/true/polygon.geojson @@ -0,0 +1,22 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], + [1, 0], + [1, 1], + [0.5, 0.5], + [0, 1], + [0, 0] + ] + ] + } + } + ] +} diff --git a/test/turf_concave/true/polygon2.geojson b/test/turf_concave/true/polygon2.geojson new file mode 100644 index 0000000..fb6e79b --- /dev/null +++ b/test/turf_concave/true/polygon2.geojson @@ -0,0 +1,28 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0.85418701171875, 1.06286628163273], + [0.46966552734375, 0.7909904981540058], + [0.6619262695312499, 0.5712795966325395], + [0.77178955078125, 0.856901647439813], + [0.736083984375, 0.8514090937773031], + [0.6591796875, 0.8129610018708315], + [0.6921386718749999, 0.884364296613886], + [0.9173583984375001, 0.8898568022677679], + [1.07391357421875, 0.8129610018708315], + [1.0876464843749998, 0.9392889790847924], + [1.0052490234375, 1.0271666545523288], + [0.85418701171875, 1.06286628163273] + ] + ] + } + } + ] +}