diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f14019e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,111 @@ +[*.cs] + +# CA2208: Instanciar exceções de argumentos corretamente +dotnet_diagnostic.CA2208.severity = none + +# IDE0059: Atribuição desnecessária de um valor +dotnet_diagnostic.IDE0059.severity = none + +[*.cs] +#### Estilos de nomenclatura #### + +# Regras de nomenclatura + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Especificações de símbolo + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Estilos de nomenclatura + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +[*.vb] +#### Estilos de nomenclatura #### + +# Regras de nomenclatura + +dotnet_naming_rule.interface_should_be_começa_com_i.severity = suggestion +dotnet_naming_rule.interface_should_be_começa_com_i.symbols = interface +dotnet_naming_rule.interface_should_be_começa_com_i.style = começa_com_i + +dotnet_naming_rule.tipos_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.tipos_should_be_pascal_case.symbols = tipos +dotnet_naming_rule.tipos_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.membros_sem_campo_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.membros_sem_campo_should_be_pascal_case.symbols = membros_sem_campo +dotnet_naming_rule.membros_sem_campo_should_be_pascal_case.style = pascal_case + +# Especificações de símbolo + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.tipos.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.tipos.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.tipos.required_modifiers = + +dotnet_naming_symbols.membros_sem_campo.applicable_kinds = property, event, method +dotnet_naming_symbols.membros_sem_campo.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.membros_sem_campo.required_modifiers = + +# Estilos de nomenclatura + +dotnet_naming_style.começa_com_i.required_prefix = I +dotnet_naming_style.começa_com_i.required_suffix = +dotnet_naming_style.começa_com_i.word_separator = +dotnet_naming_style.começa_com_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case diff --git a/Extensions/ArrayExtensions/DoubleFloatArray_AlgebraExtensions.cs b/Extensions/ArrayExtensions/DoubleFloatArray_AlgebraExtensions.cs new file mode 100644 index 0000000..a72cd35 --- /dev/null +++ b/Extensions/ArrayExtensions/DoubleFloatArray_AlgebraExtensions.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace Extensions.ArrayExtensions +{ + + + public static class DoubleFloatArray_AlgebraExtensions + { + public static double Sum(this double[] array) + { + double result = 0; + + for (int i = 0; i < array.Length; i++) + { + + result += array[i]; + } + + return result; + + } + + public static double[] Sum(this double[] array, double[] other) + { + double[] result = new double[array.Length]; + + for (int i = 0; i < array.Length; i++) + { + result[i] = array[i] + other[i]; + } + + return result; + + } + + public static double[] Subtract(this double[] array, double[] other) + { + double[] result = new double[array.Length]; + + for (int i = 0; i < array.Length; i++) + { + result[i] = array[i] - other[i]; + } + + return result; + + } + + public static double Mean(this double[] array) + { + return array.Sum() / array.Count(); + } + + /// + /// Returns the module of the array. + /// + /// The module of an array is mathematically equivalent to its own dot product. + /// + + /// + /// The Modulus (magnitude) of an array is equivalent to the squared root of the summation of its own elementwise multiplication. + /// + /// Assume a 3D array V1: {-1,2,5}. + /// + /// + /// The module of this Array will be: 1 + 4 + 25 = √30. + /// + /// + /// Equivalently, the magnitude of an array is equivalent to the squared root of its own Dot Product, and thus, it could be written as: √(V1 . V1) + /// + /// + /// + /// the Module of the array + public static double Magnitude(this double[] array) + { + var magnitude = array.DotProduct(array); + + return Math.Sqrt(magnitude); + + } + + /// + /// Alias for Magnitude: + /// + /// + /// The Modulus (magnitude) of an array is equivalent to the squared root of the summation of its own elementwise multiplication. + /// + /// Assume a 3D array V1: {-1,2,5}. + /// + /// + /// The module of this Array will be: 1 + 4 + 25 = √30. + /// + /// + /// Equivalently, the magnitude of an array is equivalent to the squared root of its own Dot Product, and thus, it could be written as: √(V1 . V1) + /// + /// + /// + /// the magnitude of the array + public static double Module(this double[] array) + { + return array.Magnitude(); + + } + + /// + /// ElementWise multiplication between two arrays. + /// + /// Assume two 3D vectors: + /// V1) {1,2,3} + /// V2) {4,5,6}; + /// + /// the returned multiplication will be: + /// V3) {4,10,18} + /// + /// + /// + /// + /// + public static double[] Multiply(this double[] array, double[] other) + { + var arrayResult = new double[array.Length]; + for (int i = 0; i < array.Length; i++) + { + arrayResult[i] = array[i] * other[i]; + } + + return arrayResult; + + } + + /// + /// ElementWise multiplication between a constant and an array. + /// + /// Assume two 3D vectors: + /// V1) {1,2,3} + /// V2) {4,5,6}; + /// + /// the returned multiplication will be: + /// V3) {4,10,18} + /// + /// + /// + /// + /// + public static double[] Multiply(this double[] array, double constant) + { + var arrayResult = new double[array.Length]; + for (int i = 0; i < array.Length; i++) + { + arrayResult[i] = array[i] * constant; + } + + return arrayResult; + + } + + /// + /// ElementWise division between two arrays. + /// + /// Assume two 3D vectors: + /// V1) {1,2,3} + /// V2) {4,5,6}; + /// + /// the returned multiplication will be: + /// V3) {4,10,18} + /// + /// + /// + /// + /// + public static double[] Divide(this double[] array, double[] other) + { + var arrayResult = new double[array.Length]; + for (int i = 0; i < array.Length; i++) + { + arrayResult[i] = array[i] / other[i]; + } + + return arrayResult; + + } + + + /// + /// ElementWise division between a constant and an array. + /// + /// Assume two 3D vectors: + /// V1) {1,2,3} + /// V2) {4,5,6}; + /// + /// the returned multiplication will be: + /// V3) {4,10,18} + /// + /// + /// + /// + /// + public static double[] Divide(this double[] array, double constant) + { + var arrayResult = new double[array.Length]; + for (int i = 0; i < array.Length; i++) + { + arrayResult[i] = array[i] / constant; + } + + return arrayResult; + + } + + /// + /// Dot Product of two arrays (scalar multiplication). + /// + /// + /// + /// + public static double DotProduct(this double[] array, double[] other) + { + var arrayResult = array.Multiply(other).Sum(); + + return arrayResult; + + } + + + public static double Determinant2D(double[] ab, double[] cd) + { + if (ab.Length != 2 || cd.Length != 2) + { + throw new InvalidOperationException("The provided arrays must of length 2"); + } + + return ( ab[0] * cd[1] ) - ( cd[0] * ab[1] ); + } + + public static double[] Determinant3D(double[] a, double[] b) + { + if (a.Length != 3 || b.Length != 3) + { + throw new InvalidOperationException("The provided arrays must of length 2"); + } + + double a2b3 = a[1] * b[2]; + double a3b2 = a[2] * b[1]; + double a3b1 = a[2] * b[0]; + double a1b3 = a[0] * b[2]; + double a1b2 = a[0] * b[1]; + double a2b1 = a[1] * b[0]; + + return new double[] { a2b3 - a3b2, + a3b1 - a1b3, + a1b2 - a2b1 }; + } + + /// + /// Cross-Product two 3D arrays (vector product). + /// + /// + /// + /// + public static double[] CrossProduct3D(this double[] array, double[] other) + { + double[] crossProduct = new double[3] { 1, 1, 1 }; + double[] a = new double[3] { 1, 1, 1 }; + double[] b = new double[3] { 1, 1, 1 }; + + for (int i = 0; i < array.Count(); i++) + { + a[i] = array[i]; + b[i] = other[i]; + } + + + if (array.Length != 2) + { + throw new InvalidOperationException("The provided arrays must of length 2"); + } + + else + { + if (a.Length != 3 || b.Length != 3) + { + throw new InvalidOperationException("The provided arrays must of length 2"); + } + + double a2b3 = a[1] * b[2]; + double a3b2 = a[2] * b[1]; + double a3b1 = a[2] * b[0]; + double a1b3 = a[0] * b[2]; + double a1b2 = a[0] * b[1]; + double a2b1 = a[1] * b[0]; + + + return new double[] {a2b3 - a3b2, a1b3 - a3b1, a1b2 - a2b1 }; + + } + } + + /// + /// Cross-Product two 2D arrays (vector product). + /// + /// + /// + /// Double + public static double CrossProduct2D(this double[] array, double[] other) + { + double result = array[0]*other[1] - array[1]*other[0]; + + return result; + } + + public static double[] GetUnitVector(this double[] array) + { + return array.Divide(array.Magnitude()); + } + + /// + /// Returns the angle between two arrays + /// + /// + /// + /// + public static double Angle(this double[] array, double[] other) + { + var theta = array.DotProduct(other) / ( array.Magnitude() * other.Magnitude() ); + + return Math.Acos(theta); + } + } +} diff --git a/Extensions/Extensions.csproj b/Extensions/Extensions.csproj new file mode 100644 index 0000000..7746344 --- /dev/null +++ b/Extensions/Extensions.csproj @@ -0,0 +1,7 @@ + + + net7.0 + enable + enable + + \ No newline at end of file diff --git a/Extensions/ListExtensions/ListExtensions.cs b/Extensions/ListExtensions/ListExtensions.cs new file mode 100644 index 0000000..06f3c13 --- /dev/null +++ b/Extensions/ListExtensions/ListExtensions.cs @@ -0,0 +1,41 @@ + +namespace Extensions.ListExtensions +{ + public static class ListExtensions + { + public static T PopAt(this List list, int index) + { + var r = list[index]; + list.RemoveAt(index); + return r; + } + + public static T Pop(this List list) + { + var r = list[0]; + list.RemoveAt(0); + return r; + } + + public static T PopFirst(this List list, Predicate predicate) + { + var index = list.FindIndex(predicate); + var r = list[index]; + list.RemoveAt(index); + return r; + } + + public static T? PopFirstOrDefault(this List list, Predicate predicate) where T : class + { + var index = list.FindIndex(predicate); + if (index > -1) + { + var r = list[index]; + list.RemoveAt(index); + return r; + } + return null; + } + + } +} diff --git a/GeoJSON.Tests/GeoJSON.Tests.csproj b/GeoJSON.Tests/GeoJSON.Tests.csproj index a0af335..e66facd 100644 --- a/GeoJSON.Tests/GeoJSON.Tests.csproj +++ b/GeoJSON.Tests/GeoJSON.Tests.csproj @@ -1,83 +1,18 @@  - - netcoreapp3.1 + net7.0 true - DEBUG;TRACE - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - + \ No newline at end of file diff --git a/GeoJSON.Tests/GeoJsonUnitTests.cs b/GeoJSON.Tests/GeoJsonUnitTests.cs deleted file mode 100644 index 8c6a047..0000000 --- a/GeoJSON.Tests/GeoJsonUnitTests.cs +++ /dev/null @@ -1,371 +0,0 @@ -using BAMCIS.GeoJSON; -using Newtonsoft.Json; -using System; -using System.IO; -using Xunit; - -namespace GeoJSON.Tests -{ - public class GeoJsonUnitTests - { - [Fact] - public void MultiLineStringTest() - { - // ARRANGE - string content = File.ReadAllText("multilinestring.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - MultiLineString geo = MultiLineString.FromJson(content); - - string content2 = geo.ToJson(); - - MultiLineString geo2 = MultiLineString.FromJson(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void LineStringTest() - { - // ARRANGE - string content = File.ReadAllText("linestring.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - LineString geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - LineString geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void MultiPolygonTest() - { - // ARRANGE - string content = File.ReadAllText("multipolygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - MultiPolygon geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - - // ASSERT - Assert.Equal(content, content2, true, true, true); - } - - [Fact] - public void FeatureTest() - { - // ARRANGE - string content = File.ReadAllText("feature.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Feature geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Feature geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void FeatureIdNotEqualTest() - { - // ARRANGE - string content1 = File.ReadAllText("feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - string content2 = File.ReadAllText("feature_id_num_as_string.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Feature geoWithNumId = JsonConvert.DeserializeObject(content1); - Feature geoWithStringId = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.Equal(geoWithNumId.Id.Value, geoWithStringId.Id.Value); - Assert.NotEqual(geoWithNumId.Id, geoWithStringId.Id); - } - - [Fact] - public void FeatureIdEqualTest() - { - // ARRANGE - string content = File.ReadAllText("feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Feature geo1 = JsonConvert.DeserializeObject(content); - Feature geo2 = JsonConvert.DeserializeObject(content); - - // ASSERT - Assert.Equal(geo1.Id.Value, geo2.Id.Value); - Assert.Equal(geo1.Id, geo2.Id); - } - - [Fact] - public void FeatureTestStringId() - { - // ARRANGE - string content = File.ReadAllText("feature_id_string.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Feature geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Feature geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void FeatureTestNumberId() - { - // ARRANGE - string content = File.ReadAllText("feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Feature geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Feature geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void FeatureOutOfRangeTest() - { - // ARRANGE - GeoJsonConfig.EnforcePositionValidation(); - string content = File.ReadAllText("feature_out_of_range.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT & ASSERT - Assert.Throws(() => JsonConvert.DeserializeObject(content)); - } - - [Fact] - public void FeatureOutOfRangeTestIgnoreValidation() - { - // ARRANGE - GeoJsonConfig.IgnorePositionValidation(); - string content = File.ReadAllText("feature_out_of_range.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Feature geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Feature geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void FeatureTestNullGeometry() - { - // ARRANGE - string content = File.ReadAllText("feature_null_geometry.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Feature geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Feature geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void FeatureCollectionTest() - { - // ARRANGE - string content = File.ReadAllText("featurecollection.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - FeatureCollection geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - FeatureCollection geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void PolygonTest() - { - // ARRANGE - string content = File.ReadAllText("polygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Polygon geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Polygon geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void PolygonWithHoleTest() - { - // ARRANGE - string content = File.ReadAllText("polygonwithhole.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Polygon geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Polygon geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void PolygonRemoveInnerRingsTestWithHole() - { - // ARRANGE - string content = File.ReadAllText("polygonwithhole.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Polygon geo = JsonConvert.DeserializeObject(content); - bool result = geo.RemoveInteriorRings(); - - - // ASSERT - Assert.True(result); - Assert.Single(geo.Coordinates); - } - - [Fact] - public void PolygonRemoveInnerRingsTestWithoutHole() - { - // ARRANGE - string content = File.ReadAllText("polygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Polygon geo = JsonConvert.DeserializeObject(content); - bool result = geo.RemoveInteriorRings(); - - - // ASSERT - Assert.False(result); - Assert.Single(geo.Coordinates); - } - - [Fact] - public void PointTest() - { - // ARRANGE - string content = File.ReadAllText("point.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Point geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - Point geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void MultiPointTest() - { - // ARRANGE - string content = File.ReadAllText("multipoint.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - MultiPoint geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - MultiPoint geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void GeometryCollectionTest() - { - // ARRANGE - string content = File.ReadAllText("geometrycollection.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - GeometryCollection geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - GeometryCollection geo2 = JsonConvert.DeserializeObject(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void PositionTest() - { - // ARRANGE - string content = File.ReadAllText("position.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - Position geo = JsonConvert.DeserializeObject(content); - string content2 = JsonConvert.SerializeObject(geo); - - // ASSERT - Assert.Equal(content, content2, true, true, true); - } - - [Fact] - public void GeoJsonFeatureTest() - { - // ARRANGE - string content = File.ReadAllText("feature.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - GeoJson geo = GeoJson.FromJson(content); - string content2 = geo.ToJson(); - GeoJson geo2 = GeoJson.FromJson(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void GeoJsonFeatureTestWithBbox() - { - // ARRANGE - string content = File.ReadAllText("featurebbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - GeoJson geo = GeoJson.FromJson(content); - string content2 = geo.ToJson(); - GeoJson geo2 = GeoJson.FromJson(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void GeoJson3DLineStringTestWithBbox() - { - // ARRANGE - string content = File.ReadAllText("3dlinestringbbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - GeoJson geo = GeoJson.FromJson(content); - string content2 = geo.ToJson(); - GeoJson geo2 = GeoJson.FromJson(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - - [Fact] - public void GeoJsonFeatureCollectionTestWithBbox() - { - // ARRANGE - string content = File.ReadAllText("featurecollectionbbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); - - // ACT - GeoJson geo = GeoJson.FromJson(content); - string content2 = geo.ToJson(); - GeoJson geo2 = GeoJson.FromJson(content2); - - // ASSERT - Assert.True(geo.Equals(geo2)); - } - } -} diff --git a/GeoJSON.Tests/3dlinestringbbox.json b/GeoJSON.Tests/ReferenceFiles/3dlinestringbbox.json similarity index 100% rename from GeoJSON.Tests/3dlinestringbbox.json rename to GeoJSON.Tests/ReferenceFiles/3dlinestringbbox.json diff --git a/GeoJSON.Tests/feature.json b/GeoJSON.Tests/ReferenceFiles/feature.json similarity index 100% rename from GeoJSON.Tests/feature.json rename to GeoJSON.Tests/ReferenceFiles/feature.json diff --git a/GeoJSON.Tests/feature_id_num_as_string.json b/GeoJSON.Tests/ReferenceFiles/feature_id_num_as_string.json similarity index 100% rename from GeoJSON.Tests/feature_id_num_as_string.json rename to GeoJSON.Tests/ReferenceFiles/feature_id_num_as_string.json diff --git a/GeoJSON.Tests/feature_id_number.json b/GeoJSON.Tests/ReferenceFiles/feature_id_number.json similarity index 100% rename from GeoJSON.Tests/feature_id_number.json rename to GeoJSON.Tests/ReferenceFiles/feature_id_number.json diff --git a/GeoJSON.Tests/feature_id_string.json b/GeoJSON.Tests/ReferenceFiles/feature_id_string.json similarity index 100% rename from GeoJSON.Tests/feature_id_string.json rename to GeoJSON.Tests/ReferenceFiles/feature_id_string.json diff --git a/GeoJSON.Tests/feature_null_geometry.json b/GeoJSON.Tests/ReferenceFiles/feature_null_geometry.json similarity index 100% rename from GeoJSON.Tests/feature_null_geometry.json rename to GeoJSON.Tests/ReferenceFiles/feature_null_geometry.json diff --git a/GeoJSON.Tests/feature_out_of_range.json b/GeoJSON.Tests/ReferenceFiles/feature_out_of_range.json similarity index 100% rename from GeoJSON.Tests/feature_out_of_range.json rename to GeoJSON.Tests/ReferenceFiles/feature_out_of_range.json diff --git a/GeoJSON.Tests/featurebbox.json b/GeoJSON.Tests/ReferenceFiles/featurebbox.json similarity index 100% rename from GeoJSON.Tests/featurebbox.json rename to GeoJSON.Tests/ReferenceFiles/featurebbox.json diff --git a/GeoJSON.Tests/featurecollection.json b/GeoJSON.Tests/ReferenceFiles/featurecollection.json similarity index 100% rename from GeoJSON.Tests/featurecollection.json rename to GeoJSON.Tests/ReferenceFiles/featurecollection.json diff --git a/GeoJSON.Tests/featurecollectionbbox.json b/GeoJSON.Tests/ReferenceFiles/featurecollectionbbox.json similarity index 100% rename from GeoJSON.Tests/featurecollectionbbox.json rename to GeoJSON.Tests/ReferenceFiles/featurecollectionbbox.json diff --git a/GeoJSON.Tests/ReferenceFiles/geometrycollection.json b/GeoJSON.Tests/ReferenceFiles/geometrycollection.json new file mode 100644 index 0000000..679d76d --- /dev/null +++ b/GeoJSON.Tests/ReferenceFiles/geometrycollection.json @@ -0,0 +1 @@ +{"geometries":[{"type":"LineString","coordinates":[[0.0,0.0],[0.0,1.0]]},{"type":"LineString","coordinates":[[1.0,0.0],[1.0,1.0]]}],"type":"GeometryCollection"} \ No newline at end of file diff --git a/GeoJSON.Tests/linestring.json b/GeoJSON.Tests/ReferenceFiles/linestring.json similarity index 100% rename from GeoJSON.Tests/linestring.json rename to GeoJSON.Tests/ReferenceFiles/linestring.json diff --git a/GeoJSON.Tests/ReferenceFiles/multilinestring.json b/GeoJSON.Tests/ReferenceFiles/multilinestring.json new file mode 100644 index 0000000..f426c83 --- /dev/null +++ b/GeoJSON.Tests/ReferenceFiles/multilinestring.json @@ -0,0 +1 @@ +{"type":"MultiLineString","coordinates":[[[0.0,0.0],[0.0,1.0]],[[1.0,0.0],[1.0,1.0]]]} \ No newline at end of file diff --git a/GeoJSON.Tests/multipoint.json b/GeoJSON.Tests/ReferenceFiles/multipoint.json similarity index 87% rename from GeoJSON.Tests/multipoint.json rename to GeoJSON.Tests/ReferenceFiles/multipoint.json index c2d4130..84d4852 100644 --- a/GeoJSON.Tests/multipoint.json +++ b/GeoJSON.Tests/ReferenceFiles/multipoint.json @@ -1,6 +1,6 @@ { "type": "MultiPoint", - "coordinates": [ + "Points": [ [ 102.0, 1.5 ], [ 103.0, 2.5 ], [ 101.0, 1.0 ], diff --git a/GeoJSON.Tests/multipolygon.json b/GeoJSON.Tests/ReferenceFiles/multipolygon.json similarity index 100% rename from GeoJSON.Tests/multipolygon.json rename to GeoJSON.Tests/ReferenceFiles/multipolygon.json diff --git a/GeoJSON.Tests/point.json b/GeoJSON.Tests/ReferenceFiles/point.json similarity index 100% rename from GeoJSON.Tests/point.json rename to GeoJSON.Tests/ReferenceFiles/point.json diff --git a/GeoJSON.Tests/ReferenceFiles/polygon.json b/GeoJSON.Tests/ReferenceFiles/polygon.json new file mode 100644 index 0000000..5de004f --- /dev/null +++ b/GeoJSON.Tests/ReferenceFiles/polygon.json @@ -0,0 +1 @@ +{"type":"Polygon","coordinates":[[[40.0,40.0],[20.0,45.0],[45.0,30.0],[40.0,40.0]]]} \ No newline at end of file diff --git a/GeoJSON.Tests/polygonwithhole.json b/GeoJSON.Tests/ReferenceFiles/polygonwithhole.json similarity index 100% rename from GeoJSON.Tests/polygonwithhole.json rename to GeoJSON.Tests/ReferenceFiles/polygonwithhole.json diff --git a/GeoJSON.Tests/position.json b/GeoJSON.Tests/ReferenceFiles/position.json similarity index 100% rename from GeoJSON.Tests/position.json rename to GeoJSON.Tests/ReferenceFiles/position.json diff --git a/GeoJSON.Tests/Testers/GeoJsonBaseUnitTester.cs b/GeoJSON.Tests/Testers/GeoJsonBaseUnitTester.cs new file mode 100644 index 0000000..ca41ef0 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonBaseUnitTester.cs @@ -0,0 +1,42 @@ +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace GeoJSON.Tests.Tests +{ + public class GeoJsonBaseUnitTester: WkbBaseUnitTester + { + + /// + /// General method for writing geometryContent into a file. + /// + /// + /// + protected static void WriteJsonFile(string filename, string geometryContent ) + { + var filepath = Path.Join(GetCurrentWorkingDirectory(), filename); + Debug.WriteLine($"Saving content into {filepath}"); + + File.WriteAllText(filepath, geometryContent); + } + + protected static string ReadJsonFile(string filename) + { + var filepath = Path.Join(GetCurrentWorkingDirectory(), filename); + Debug.WriteLine($"Reading content from {filepath}"); + + return File.ReadAllText(filepath); + } + + protected static string GetCurrentWorkingDirectory() + { + var cwddirname = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + var ToDirname = cwddirname.Split("bin")[0]; + + return ToDirname; + + + } + } +} \ No newline at end of file diff --git a/GeoJSON.Tests/Testers/GeoJsonFeatureCollectionUnitTester.cs b/GeoJSON.Tests/Testers/GeoJsonFeatureCollectionUnitTester.cs new file mode 100644 index 0000000..2bd34a6 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonFeatureCollectionUnitTester.cs @@ -0,0 +1,73 @@ +using BAMCIS.GeoJSON; +using GeoJSON.Tests.Tests; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using Xunit; + +namespace GeoJSON.Tests.Testers +{ + public class GeoJsonFeatureCollectionUnitTester : GeoJsonBaseUnitTester + { + + [Fact] + public void FeatureCollectionTest() + { + // ARRANGE + FeatureCollection featCollection = FetchDefaultFeaturecollection(); + string content = JsonConvert.SerializeObject(featCollection); + Debug.WriteLine(content); + + // ACT + FeatureCollection geo = JsonConvert.DeserializeObject(content); + string contentDeserialized = JsonConvert.SerializeObject(geo); + Debug.WriteLine(contentDeserialized); + + Assert.True(contentDeserialized.Equals(content)); + + + FeatureCollection geoAfterDesrialization = JsonConvert.DeserializeObject(contentDeserialized); + + // ASSERT + Assert.True(geo.Equals(geoAfterDesrialization)); + } + + private static FeatureCollection FetchDefaultFeaturecollection() + { + var linearRing = new LinearRing(new List{ + new LineSegment(new Point(100,0), + new Point(101,0) + ), + + new LineSegment(new Point(101,0), + new Point(101,1) + ), + + new LineSegment(new Point(101,1), + new Point(100,1) + ), + + new LineSegment(new Point(100,1), + new Point(100,0) + ) + }); + + var polygon = new Polygon(linearRing); + + + var LineString = new LineString(new List { new Point(102.0, 0.0), new Point(103.0, 1.0) }); + + var point = new Point(102, 0.5); + + + var featCol = new FeatureCollection(new List { new Feature(point, new Dictionary{ {"PropertyToTest", "Test" } } ), + new Feature(LineString, new Dictionary{ {"PropertyToTest", "Test" } }), + new Feature(polygon, new Dictionary{ {"PropertyToTest", "Test" } }) + }); + + return featCol; + + } + } +} diff --git a/GeoJSON.Tests/Testers/GeoJsonFeatureUnitTester.cs b/GeoJSON.Tests/Testers/GeoJsonFeatureUnitTester.cs new file mode 100644 index 0000000..96d54fa --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonFeatureUnitTester.cs @@ -0,0 +1,134 @@ +using BAMCIS.GeoJSON; +using GeoJSON.Tests.Tests; +using Newtonsoft.Json; +using System; +using System.Diagnostics; +using System.IO; +using Xunit; + +namespace GeoJSON.Tests.Testers +{ + public class GeoJsonFeatureUnitTester : GeoJsonBaseUnitTester + { + + [Fact] + public void FeatureTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/feature.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Feature geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Feature geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void FeatureIdNotEqualTest() + { + // ARRANGE + string content1 = File.ReadAllText("ReferenceFiles/feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + string content2 = File.ReadAllText("ReferenceFiles/feature_id_num_as_string.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Feature geoWithNumId = JsonConvert.DeserializeObject(content1); + Feature geoWithStringId = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.Equal(geoWithNumId.Id.Value, geoWithStringId.Id.Value); + Assert.NotEqual(geoWithNumId.Id, geoWithStringId.Id); + } + + [Fact] + public void FeatureIdEqualTest() + { + Debug.WriteLine(GetCurrentWorkingDirectory()); + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Feature geo1 = JsonConvert.DeserializeObject(content); + Feature geo2 = JsonConvert.DeserializeObject(content); + + // ASSERT + Assert.Equal(geo1.Id.Value, geo2.Id.Value); + Assert.Equal(geo1.Id, geo2.Id); + } + + [Fact] + public void FeatureTestStringId() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/feature_id_string.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Feature geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Feature geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void FeatureTestNumberId() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Feature geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Feature geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void FeatureOutOfRangeTest() + { + // ARRANGE + GeoJsonConfig.EnforcePositionValidation(); + string content = File.ReadAllText("ReferenceFiles/feature_out_of_range.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT & ASSERT + Assert.Throws(() => JsonConvert.DeserializeObject(content)); + } + + [Fact] + public void FeatureOutOfRangeTestIgnoreValidation() + { + // ARRANGE + GeoJsonConfig.IgnorePositionValidation(); + string content = File.ReadAllText("ReferenceFiles/feature_out_of_range.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Feature geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Feature geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void FeatureTestNullGeometry() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/feature_null_geometry.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Feature geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Feature geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + } +} diff --git a/GeoJSON.Tests/Testers/GeoJsonLineStringUnitTester.cs b/GeoJSON.Tests/Testers/GeoJsonLineStringUnitTester.cs new file mode 100644 index 0000000..1a5f331 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonLineStringUnitTester.cs @@ -0,0 +1,66 @@ +using BAMCIS.GeoJSON; +using GeoJSON.Tests.Tests; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace GeoJSON.Tests.Testers +{ + public class GeoJsonLineStringUnitTester : GeoJsonBaseUnitTester + { + + private LineString FetchDefaultLineString() + { + var p1 = new Point(1, 1); + + var p2 = new Point(2, 5); + + var p3 = new Point(5, 5); + + var p4 = new Point(8, 5); + + var lineSegment = new LineSegment(p1, p2); + + var lineSegment2 = new LineSegment(p2, p3); + + var lineSegment3 = new LineSegment(p3, p4); + + var lineString = new LineString(new List { lineSegment, lineSegment2, lineSegment3 }); + + return lineString; + } + + [Fact] + public void LineStringTouchesPoint() + { + // ARRANGE + var lineString = FetchDefaultLineString(); + + var p1 = new Point(2, 5); + var p2 = new Point(6, 5); + // ACT + + + // ASSERT + Assert.False(lineString.Equals(p1)); + Assert.True(lineString.Touches(p1)); + Assert.False(lineString.Intersects(p1)); + Assert.True(lineString.Touches(p2)); + Assert.False(lineString.Intersects(p1)); + } + + [Fact] + public void LineStringEqualityEvaluation() + { + // ARRANGE + var lineString = FetchDefaultLineString(); + var lineString2 = FetchDefaultLineString(); + + // ASSERT + Assert.True(lineString.Equals(lineString2)); + } + + } +} diff --git a/GeoJSON.Tests/Testers/GeoJsonMultiLineUnitTester.cs b/GeoJSON.Tests/Testers/GeoJsonMultiLineUnitTester.cs new file mode 100644 index 0000000..9e1ad22 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonMultiLineUnitTester.cs @@ -0,0 +1,68 @@ +using BAMCIS.GeoJSON; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Xunit; + +namespace GeoJSON.Tests.Tests +{ + public class GeoJsonMultiLineUnitTester : GeoJsonBaseUnitTester + { + protected MultiLineString FetchDefaultGeometry() + { + var line1 = new LineString(new List{ new Point(0,0), new Point(0,1) }); + + var line2 = new LineString(new List { new Point(1, 0), new Point(1, 1) }); + + + return new MultiLineString(new List{ line1, line2 }); + } + + [Fact] + public void MultiLineStringTest() + { + // ARRANGE + var multiLineString = FetchDefaultGeometry(); + var content = multiLineString.ToJson(); + + WriteJsonFile("ReferenceFiles/multilinestring.json", content); + + content = ReadJsonFile("ReferenceFiles/multilinestring.json"); + + // ACT + MultiLineString geo = MultiLineString.FromJson(content); + + string content2 = geo.ToJson(); + + MultiLineString geo2 = MultiLineString.FromJson(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + + [Fact] + public void MultiLineStringWritingTest() + { + // ACT + + LineString lineString1 = GeometriesDefaultGenerator.GenerateDefaultLineStringForUnitTests(); + + LineString lineString2 = GeometriesDefaultGenerator.GenerateDefaultLineStringForUnitTests(3, 5, 2); + + + var multiLineString = new MultiLineString(new List { lineString1, lineString2 }); + + + string content2 = JsonConvert.SerializeObject(multiLineString, Formatting.Indented); + + string ToDirname = GetCurrentWorkingDirectory(); + + File.WriteAllText(Path.Combine(ToDirname, "multilinestring.json"), content2); + + // File.Delete(ToDirname, "multilinestring.json")); + + } + } +} \ No newline at end of file diff --git a/GeoJSON.Tests/Testers/GeoJsonMultiPolygonUnitTester.cs b/GeoJSON.Tests/Testers/GeoJsonMultiPolygonUnitTester.cs new file mode 100644 index 0000000..9f67450 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonMultiPolygonUnitTester.cs @@ -0,0 +1,26 @@ +using BAMCIS.GeoJSON; +using Newtonsoft.Json; +using System.IO; +using Xunit; + +namespace GeoJSON.Tests.Testers +{ + public class GeoJsonMultiPolygonUnitTester + { + + [Fact] + public void MultiPolygonTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/multipolygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + MultiPolygon geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + + // ASSERT + Assert.Equal(content, content2, true, true, true); + } + + } +} diff --git a/GeoJSON.Tests/Testers/GeoJsonPolygonUnitTester.cs b/GeoJSON.Tests/Testers/GeoJsonPolygonUnitTester.cs new file mode 100644 index 0000000..254d6f3 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonPolygonUnitTester.cs @@ -0,0 +1,240 @@ +using BAMCIS.GeoJSON; +using GeoJSON.Tests.Tests; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace GeoJSON.Tests.Testers +{ + public class GeoJsonPolygonUnitTester : GeoJsonBaseUnitTester + { + #region Helper Methods + private static Polygon FetchDefaultPolygon() + { + var coordinates = new List { new Coordinate(40, 40) , + new Coordinate(20,45) , + new Coordinate(45, 30) , + new Coordinate(40,40) + }; + + var linearRing = new LinearRing(coordinates); + + var polygon = new Polygon(linearRing); + + return polygon; + } + + /// + /// This method generates a regular rectangle (therefore, a polygon) + /// ranging from the coordinate 1,1 (lower left corner) up to the coordinate 4,4 (Upper right corner) + /// + /// + private static Polygon FetchDefaultRectangle() + { + var coordinates = new List { new Coordinate(1, 1) , + new Coordinate(1, 4) , + new Coordinate(4, 4) , + new Coordinate(4, 1) , + new Coordinate(1, 1) + }; + + var linearRing = new LinearRing(coordinates); + + var polygon = new Polygon(linearRing); + + return polygon; + } + + + private static Polygon FetchIrregularPolygon() + { + var coordinates = new List { new Coordinate(1, 1) , + new Coordinate(1, 4) , + new Coordinate(4, 4) , + new Coordinate(6, 4) , + new Coordinate(5, -2) , + new Coordinate(3.5, 2.5) , + new Coordinate(3.25, 2.4) , + new Coordinate(2, -2), + new Coordinate(1, 1) + }; + + var linearRing = new LinearRing(coordinates); + + var polygon = new Polygon(linearRing); + + return polygon; + } + + #endregion Helper Methods + + #region Test Methods + + [Fact] + public void SerializePolygon() + { + // ARRANGE + + var polygon = FetchDefaultPolygon(); + string polygonContent = JsonConvert.SerializeObject(polygon); + WriteJsonFile("ReferenceFiles/Polygon.json", polygonContent); + + string content = ReadJsonFile("ReferenceFiles/Polygon.json"); + + // ACT + var PolygonReconstituted = JsonConvert.DeserializeObject(content); + + + // ASSERT + Assert.True(polygon.Equals(PolygonReconstituted)); + } + + [Fact] + public void PolygonContainsPoint() + { + + // ARRANGE + + var regularPolygon = FetchDefaultRectangle(); + + var irregularPolygon = FetchIrregularPolygon(); + + var point = new Point(2, 2); + + var point2 = new Point(5, 2); + + var point3 = new Point(3, -2.5); // this point must be outside of the irregularpolygon, and also outside of its bounding box + + var point4 = new Point(3.5, 0.5); // this point must be outside of the irregularpolygon, though inside of its bounding box + + var point5 = new Point(3.5, 2.5); // this points must touch the irregularPolygon, not be inside of the polygon, nor touch its bounding box + + // ASSERT + + #region Regular Polygon Validations + + Assert.True(regularPolygon.Contains(point)); + Assert.False(regularPolygon.Touches(point)); + + Assert.False(regularPolygon.Contains(point2)); + Assert.False(regularPolygon.Touches(point2)); + + #endregion Regular Polygon Validations + + #region Irregular Polygon Validations + Assert.True(!irregularPolygon.Contains(point3) && + !irregularPolygon.BoundingBox.Contains(point3) && + !irregularPolygon.Intersects(point3) && + !irregularPolygon.BoundingBox.Touches(point3)); + + Assert.True(!irregularPolygon.Contains(point4)); + + Assert.True(irregularPolygon.BoundingBox.Contains(point4)); + Assert.False(irregularPolygon.Intersects(point4)); + Assert.False(irregularPolygon.BoundingBox.Touches(point4)); + + + + Assert.False(irregularPolygon.Contains(point5)); + + Assert.True(irregularPolygon.BoundingBox.Contains(point5)); + + Assert.True(irregularPolygon.Touches(point5)); + + Assert.False(irregularPolygon.BoundingBox.Touches(point5)); + + #endregion Irregular Polygon Validations + + } + + [Fact] + public void PolygonIntersectsPoint() + { + + // ARRANGE + + var polygon = FetchDefaultRectangle(); + + var point = new Point(1, 1); + + + // ASSERT + Assert.False(polygon.Intersects(point)); + + } + + + + [Fact] + public void PolygonContainsLineSegment() + { + + // ARRANGE + + var polygon = FetchDefaultRectangle(); + + var point = new Point(2, 2); + + var point2 = new Point(5, 2); + + // ASSERT + Assert.True(polygon.Contains(point)); + Assert.False(polygon.Touches(point)); + + Assert.False(polygon.Contains(point2)); + Assert.False(polygon.Touches(point2)); + + } + + [Fact] + public void IntersectsLineSegment() + { + + // ARRANGE + + var polygon = FetchDefaultRectangle(); + + var point = new Point(1, 1); + + var lineSegment = new LineSegment(point, new Point(5, 5)); + + + // ASSERT + Assert.True(polygon.Intersects(lineSegment)); + Assert.False(polygon.Contains(lineSegment)); + + } + + + [Fact] + public void IntersectsLineString() + { + + // ARRANGE + + var polygon = FetchDefaultRectangle(); + + var p1 = new Point(1, 1); + + var p2 = new Point(2, 5); + + var p3 = new Point(5, 5); + + var lineSegment = new LineSegment(p1, p2); + + var lineSegment2 = new LineSegment(p2, p3); + + var lineString = new LineString(new List {lineSegment,lineSegment2 }); + + + // ASSERT + Assert.True(polygon.Intersects(lineString)); + Assert.False(polygon.Contains(lineString)); + } + + #endregion Test Methods + + } +} diff --git a/GeoJSON.Tests/Testers/GeoJsonUnitTests.cs b/GeoJSON.Tests/Testers/GeoJsonUnitTests.cs new file mode 100644 index 0000000..9c04ef4 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeoJsonUnitTests.cs @@ -0,0 +1,206 @@ +using BAMCIS.GeoJSON; +using Newtonsoft.Json; +using System; +using System.IO; +using Xunit; + +namespace GeoJSON.Tests.Tests +{ + public class GeoJsonUnitTests : GeoJsonMultiLineUnitTester + { + + [Fact] + public void PolygonTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/polygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Polygon geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Polygon geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void PolygonWithHoleTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/polygonwithhole.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Polygon geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Polygon geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void PolygonRemoveInnerRingsTestWithHole() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/polygonwithhole.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + + Polygon geo = JsonConvert.DeserializeObject(content); + geo.RemoveInteriorRings(); + + // ASSERT + Assert.Single(geo.LinearRings); + + + Assert.True(true); + + + + } + + [Fact] + public void PolygonRemoveInnerRingsTestWithoutHole() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/polygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Polygon geo = JsonConvert.DeserializeObject(content); + geo.RemoveInteriorRings(); + + + // ASSERT + Assert.Single(geo.LinearRings); + } + + [Fact] + public void PointTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/point.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Point geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + Point geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void MultiPointTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/multipoint.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + var geo = MultiPoint.FromJson(content); + // ACT + // MultiPoint geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + MultiPoint geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void GeometryCollectionTest() + { + // ARRANGE + + var multiLineString = FetchDefaultGeometry(); + + var geometryCollection = new GeometryCollection(multiLineString.ToList()); + + var content = geometryCollection.ToJson(); + + WriteJsonFile("ReferenceFiles/geometrycollection.json", content); + + content = ReadJsonFile("ReferenceFiles/geometrycollection.json"); + + // ACT + GeometryCollection geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + GeometryCollection geo2 = JsonConvert.DeserializeObject(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void PositionTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/position.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + Coordinate geo = JsonConvert.DeserializeObject(content); + string content2 = JsonConvert.SerializeObject(geo); + + // ASSERT + Assert.Equal(content, content2, true, true, true); + } + + [Fact] + public void GeoJsonFeatureTest() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/feature.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + GeoJson geo = GeoJson.FromJson(content); + string content2 = geo.ToJson(); + GeoJson geo2 = GeoJson.FromJson(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void GeoJsonFeatureTestWithBbox() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/featurebbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + GeoJson geo = GeoJson.FromJson(content); + string content2 = geo.ToJson(); + GeoJson geo2 = GeoJson.FromJson(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void GeoJson3DLineStringTestWithBbox() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/3dlinestringbbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + GeoJson geo = GeoJson.FromJson(content); + string content2 = geo.ToJson(); + GeoJson geo2 = GeoJson.FromJson(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + + [Fact] + public void GeoJsonFeatureCollectionTestWithBbox() + { + // ARRANGE + string content = File.ReadAllText("ReferenceFiles/featurecollectionbbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); + + // ACT + GeoJson geo = GeoJson.FromJson(content); + string content2 = geo.ToJson(); + GeoJson geo2 = GeoJson.FromJson(content2); + + // ASSERT + Assert.True(geo.Equals(geo2)); + } + } +} diff --git a/GeoJSON.Tests/Testers/GeometriesDefaultGenerator.cs b/GeoJSON.Tests/Testers/GeometriesDefaultGenerator.cs new file mode 100644 index 0000000..0c161b4 --- /dev/null +++ b/GeoJSON.Tests/Testers/GeometriesDefaultGenerator.cs @@ -0,0 +1,47 @@ +using BAMCIS.GeoJSON; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GeoJSON.Tests.Tests +{ + internal static class GeometriesDefaultGenerator + { + + internal static LineString GenerateDefaultLineStringForUnitTests(int maxLineSegmentsPerLineString = 4, int x0 = 0, int y0 = 0) + { + var lineSegments = new List(); + + for (int lineSegmentCounter = 0; lineSegmentCounter < maxLineSegmentsPerLineString; lineSegmentCounter++) + { + for (int x = x0; x < x0 + 2; x++) + { + int y = 0; + var p1 = new Point(new Coordinate(x, y)); + + for (y = y0; y < y0 + 2; y++) + { + var p2 = new Point(new Coordinate(x, y)); + + if (p1 == p2) + { + + } + else + { + var lineSegment = new LineSegment(p1, p2); + lineSegments.Add(lineSegment); + } + } + } + } + + var geo = new LineString(lineSegments); + return geo; + } + + + } +} diff --git a/GeoJSON.Tests/Testers/WkbGeometryCollectionUnitTester.cs b/GeoJSON.Tests/Testers/WkbGeometryCollectionUnitTester.cs new file mode 100644 index 0000000..b7f2a95 --- /dev/null +++ b/GeoJSON.Tests/Testers/WkbGeometryCollectionUnitTester.cs @@ -0,0 +1,108 @@ +using BAMCIS.GeoJSON; +using BAMCIS.GeoJSON.Wkb; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace GeoJSON.Tests.Testers +{ + public class WkbGeometryCollectionUnitTester : WkbBaseUnitTester + { + #region geometry generator + /// + /// GEOMETRYCOLLECTION(POINT (40 10),LINESTRING(10 10, 20 20, 10 40),POLYGON((40 40, 20 45, 45 30, 40 40))) + /// + /// + /// + /// + /// + private static void FetchDefaultGeometryCollectionTest(out Point point, out LinearRing linearRing, out Polygon polygon, out GeometryCollection geomColl) + { + point = new Point(40, 10); + var lineString = new LineString(new List { new Point(10, 10), new Point(20, 20), new Point(10, 40) }); + + var coordinates = new List { new Coordinate(40, 40) , + new Coordinate(20,45) , + new Coordinate(45, 30) , + new Coordinate(40,40) + }; + + linearRing = new LinearRing(coordinates); + polygon = new Polygon(linearRing); + geomColl = new GeometryCollection(new List { point, lineString, polygon }); + } + + + #endregion geometry generator + + + #region Tests + + [Fact] + public void GeometryCollectionTest_FromBinary_BigEndian() + { + // ARRANGE + + + FetchDefaultGeometryCollectionTest(out Point point, + out LinearRing linearRing, + out Polygon polygon, + out GeometryCollection geomColl); + + // ACT + byte[] bytes = WkbConverter.ToBinary(geomColl); + + Geometry geo = WkbConverter.FromBinary(bytes); + + GeometryCollection geoCollection = Assert.IsType(geo); + Point pointReconstituted = Assert.IsType(geoCollection.Geometries.ElementAt(0)); + LineString lineStringReconstituted = Assert.IsType(geoCollection.Geometries.ElementAt(1)); + Polygon polygonReconstituted = Assert.IsType(geoCollection.Geometries.ElementAt(2)); + + + // ASSERT + + Assert.True(lineStringReconstituted.Equals(linearRing), "Reconstituted LinearRing is not equivalent to the original one"); + Assert.True(polygonReconstituted.Equals(polygon), "Reconstituted Polygon is not equivalent to the original one"); + Assert.True(pointReconstituted.Equals(point), "Reconstituted Point is not equivalent to the original one"); + Assert.Equal(40, point.Coordinates.Longitude); + Assert.Equal(10, point.Coordinates.Latitude); + } + + + + [Fact] + public void GeometryCollectionTest_ToBinary_BigEndian() + { + // ARRANGE + + WkbGeometryCollectionUnitTester.FetchDefaultGeometryCollectionTest(out Point point, + out LinearRing linearRing, + out Polygon polygon, + out GeometryCollection geomColl); + + // ACT + byte[] bytes = WkbConverter.ToBinary(geomColl, Endianness.BIG); + + + Geometry geo = WkbConverter.FromBinary(bytes); + + + GeometryCollection geoCollection = Assert.IsType(geo); + Point pointReconstituted = Assert.IsType(geoCollection.Geometries.ElementAt(0)); + LineString lineStringReconstituted = Assert.IsType(geoCollection.Geometries.ElementAt(1)); + Polygon polygonReconstituted = Assert.IsType(geoCollection.Geometries.ElementAt(2)); + + + // ASSERT + + Assert.True(lineStringReconstituted.Equals(linearRing), "Reconstituted LinearRing is not equivalent to the original one"); + Assert.True(polygonReconstituted.Equals(polygon), "Reconstituted Polygon is not equivalent to the original one"); + Assert.True(pointReconstituted.Equals(point), "Reconstituted Point is not equivalent to the original one"); + Assert.Equal(40, point.Coordinates.Longitude); + Assert.Equal(10, point.Coordinates.Latitude); + } + + #endregion Tests + } +} \ No newline at end of file diff --git a/GeoJSON.Tests/WkbBaseUnitTester.cs b/GeoJSON.Tests/WkbBaseUnitTester.cs new file mode 100644 index 0000000..721871a --- /dev/null +++ b/GeoJSON.Tests/WkbBaseUnitTester.cs @@ -0,0 +1,37 @@ +using System.Globalization; +using System; + +namespace GeoJSON.Tests +{ + public class WkbBaseUnitTester + { + + #region Convertion Methods + + protected static byte[] HexStringToByteArray(string hexString) + { + if (hexString.Length % 2 != 0) + { + throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", hexString)); + } + + byte[] data = new byte[hexString.Length / 2]; + + for (int index = 0; index < data.Length; index++) + { + string byteValue = hexString.Substring(index * 2, 2); + data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + return data; + } + + protected static string ByteArrayTohexString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } + + #endregion Convertion Methods + + } +} \ No newline at end of file diff --git a/GeoJSON.Tests/WkbPointUnitTester.cs b/GeoJSON.Tests/WkbPointUnitTester.cs new file mode 100644 index 0000000..9ff688d --- /dev/null +++ b/GeoJSON.Tests/WkbPointUnitTester.cs @@ -0,0 +1,133 @@ +using BAMCIS.GeoJSON; +using BAMCIS.GeoJSON.Wkb; +using Xunit; + +namespace GeoJSON.Tests +{ + public class WkbPointUnitTester : WkbBaseUnitTester + { + [Fact] + public void WkbConverterTest_ToBinary_BigEndian() + { + // ARRANGE + byte[] expectedBytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + var point = new Point(new Coordinate(2.0, 4.0)); + + // ACT + byte[] bytes = WkbConverter.ToBinary(point, Endianness.BIG); + + // ASSERT + Assert.Equal(expectedBytes, bytes); + } + + [Fact] + public void WkbConverterTest_FromBinary_BigEndian() + { + // ARRANGE + byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + + // ACT + Point point = WkbConverter.FromBinary(bytes); + + // ASSERT + Assert.Equal(2.0, point.GetLongitude()); + Assert.Equal(4.0, point.GetLatitude()); + } + + [Fact] + public void PointTest_Conversion() + { + // ARRANGE + var point = new Point(new Coordinate(10.0, 10.0)); + + // ACT + byte[] bytes = point.ToWkb(); + Geometry geo = Point.FromWkb(bytes); + + // ASSERT + point = Assert.IsType(geo); + } + + [Fact] + public void PointTest_Conversion2() + { + // ARRANGE + var point = new Point(new Coordinate(10.0, 10.0)); + + // ACT + byte[] bytes = point.ToWkb(); + point = Geometry.FromWkb(bytes); + + // ASSERT + point = Assert.IsType(point); + Assert.Equal(10.0, point.GetLongitude()); + Assert.Equal(10.0, point.GetLatitude()); + } + + [Fact] + public void PointTest_FromBinary_BigEndian() + { + // ARRANGE + + // POINT(2.0 4.0) BIG ENDIAN + byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + Point point = Assert.IsType(geo); + Assert.Equal(2.0, point.Coordinates.Longitude); + Assert.Equal(4.0, point.Coordinates.Latitude); + } + + [Fact] + public void PointTest_FromBinary_LittleEndian() + { + // ARRANGE + + // POINT(1.2345 2.3456) LITTLE ENDIAN + byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + Point point = Assert.IsType(geo); + Assert.Equal(1.2345, point.Coordinates.Longitude); + Assert.Equal(2.3456, point.Coordinates.Latitude); + } + + [Fact] + public void PointTest_ToBinary_BigEndian() + { + // ARRANGE + + // POINT(2.0 4.0) BIG ENDIAN + byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void PointTest_ToBinary_LittleEndian() + { + // ARRANGE + + // POINT(1.2345 2.3456) LITTLE ENDIAN + byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + } +} \ No newline at end of file diff --git a/GeoJSON.Tests/WkbUnitTests.cs b/GeoJSON.Tests/WkbUnitTests.cs index c8cafbe..cad1f4f 100644 --- a/GeoJSON.Tests/WkbUnitTests.cs +++ b/GeoJSON.Tests/WkbUnitTests.cs @@ -9,141 +9,9 @@ namespace GeoJSON.Tests { - public class WkbUnitTests + public class WkbUnitTests : WkbBaseUnitTester { - #region WkbConverter Tests - - [Fact] - public void WkbConverterTest_ToBinary_BigEndian() - { - // ARRANGE - byte[] expectedBytes = HexStringToByteArray("000000000140000000000000004010000000000000"); - Point point = new Point(new Position(2.0, 4.0)); - - // ACT - byte[] bytes = WkbConverter.ToBinary(point, Endianness.BIG); - - // ASSERT - Assert.Equal(expectedBytes, bytes); - } - - [Fact] - public void WkbConverterTest_FromBinary_BigEndian() - { - // ARRANGE - byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); - - // ACT - Point point = WkbConverter.FromBinary(bytes); - - // ASSERT - Assert.Equal(2.0, point.GetLongitude()); - Assert.Equal(4.0, point.GetLatitude()); - } - - #endregion - - #region Point Tests - - [Fact] - public void PointTest_Conversion() - { - // ARRANGE - Point point = new Point(new Position(10.0, 10.0)); - - // ACT - byte[] bytes = point.ToWkb(); - Geometry geo = Point.FromWkb(bytes); - - // ASSERT - point = Assert.IsType(geo); - } - - [Fact] - public void PointTest_Conversion2() - { - // ARRANGE - Point point = new Point(new Position(10.0, 10.0)); - - // ACT - byte[] bytes = point.ToWkb(); - point = Geometry.FromWkb(bytes); - - // ASSERT - point = Assert.IsType(point); - Assert.Equal(10.0, point.GetLongitude()); - Assert.Equal(10.0, point.GetLatitude()); - } - - [Fact] - public void PointTest_FromBinary_BigEndian() - { - // ARRANGE - - // POINT(2.0 4.0) BIG ENDIAN - byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - - // ASSERT - Point point = Assert.IsType(geo); - Assert.Equal(2.0, point.Coordinates.Longitude); - Assert.Equal(4.0, point.Coordinates.Latitude); - } - - [Fact] - public void PointTest_ToBinary_BigEndian() - { - // ARRANGE - - // POINT(2.0 4.0) BIG ENDIAN - byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); - - // ASSERT - Assert.Equal(bytes, newBytes); - } - - [Fact] - public void PointTest_FromBinary_LittleEndian() - { - // ARRANGE - - // POINT(1.2345 2.3456) LITTLE ENDIAN - byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - - // ASSERT - Point point = Assert.IsType(geo); - Assert.Equal(1.2345, point.Coordinates.Longitude); - Assert.Equal(2.3456, point.Coordinates.Latitude); - } - - [Fact] - public void PointTest_ToBinary_LittleEndian() - { - // ARRANGE - - // POINT(1.2345 2.3456) LITTLE ENDIAN - byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); - - // ASSERT - Assert.Equal(bytes, newBytes); - } - - #endregion - - #region LineString Tests + [Fact] public void LineStringTest_FromBinary_BigEndian() @@ -158,10 +26,10 @@ public void LineStringTest_FromBinary_BigEndian() // ASSERT LineString lineString = Assert.IsType(geo); - Assert.Equal(3, lineString.Coordinates.Count()); - Assert.Equal(new Position(30, 10), lineString.Coordinates.ElementAt(0)); - Assert.Equal(new Position(10, 30), lineString.Coordinates.ElementAt(1)); - Assert.Equal(new Position(40, 40), lineString.Coordinates.ElementAt(2)); + Assert.True(lineString.LineSegments.Count() == 2, "Length of the LineSegment does not match"); + Assert.Equal(new Coordinate(30, 10), lineString.LineSegments.ElementAt(0).ElementAt(0).Coordinates); + Assert.Equal(new Coordinate(10, 30), lineString.LineSegments.ElementAt(1).ElementAt(0).Coordinates); + Assert.Equal(new Coordinate(40, 40), lineString.LineSegments.ElementAt(1).ElementAt(1).Coordinates); } [Fact] @@ -177,10 +45,10 @@ public void LineStringTest_FromBinary_LittleEndian() // ASSERT LineString lineString = Assert.IsType(geo); - Assert.Equal(3, lineString.Coordinates.Count()); - Assert.Equal(new Position(30, 10), lineString.Coordinates.ElementAt(0)); - Assert.Equal(new Position(10, 30), lineString.Coordinates.ElementAt(1)); - Assert.Equal(new Position(40, 40), lineString.Coordinates.ElementAt(2)); + Assert.True(lineString.LineSegments.Count() == 2, "Length of the LineSegment does not match"); + Assert.Equal(new Coordinate(30, 10), lineString.LineSegments.ElementAt(0).ElementAt(0).Coordinates); + Assert.Equal(new Coordinate(10, 30), lineString.LineSegments.ElementAt(1).ElementAt(0).Coordinates); + Assert.Equal(new Coordinate(40, 40), lineString.LineSegments.ElementAt(1).ElementAt(1).Coordinates); } [Fact] @@ -196,10 +64,10 @@ public void LineStringTestWithDoubles_FromBinary_BigEndian() // ASSERT LineString lineString = Assert.IsType(geo); - Assert.Equal(5, lineString.Coordinates.Count()); - Assert.Equal(new Position(30.1234, 10.6), lineString.Coordinates.ElementAt(0)); - Assert.Equal(new Position(10.77, 30.85), lineString.Coordinates.ElementAt(1)); - Assert.Equal(new Position(19, 77), lineString.Coordinates.ElementAt(4)); + Assert.Equal(4, lineString.LineSegments.Count()); + Assert.Equal(new Coordinate(30.1234, 10.6), lineString.LineSegments.ElementAt(0).ElementAt(0).Coordinates); + Assert.Equal(new Coordinate(10.77, 30.85), lineString.LineSegments.ElementAt(1).ElementAt(0).Coordinates); + Assert.Equal(new Coordinate(19, 77), lineString.LineSegments.ElementAt(3).ElementAt(1).Coordinates); } [Fact] @@ -207,15 +75,14 @@ public void LineStringTest_ToBinary_BigEndian() { // ARRANGE - // LINESTRING(30 10, 10 30, 40 40) - byte[] bytes = HexStringToByteArray("000000000200000003403E00000000000040240000000000004024000000000000403E00000000000040440000000000004044000000000000"); + var geo = new LineString(new List{ new Point(new Coordinate(30, 10)), new Point(new Coordinate(10, 30)), new Point(new Coordinate(40, 40)) } ); // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + byte[] bytes = WkbConverter.ToBinary(geo, Endianness.BIG); + Geometry geoReconstituted = WkbConverter.FromBinary(bytes); // ASSERT - Assert.Equal(bytes, newBytes); + Assert.True(geo.Equals(geoReconstituted), "Geometries are not equivalent."); } [Fact] @@ -223,15 +90,14 @@ public void LineStringTest_ToBinary_LittleEndian() { // ARRANGE - // LINESTRING(30 10, 10 30, 40 40) - byte[] bytes = HexStringToByteArray("0102000000030000000000000000003e40000000000000244000000000000024400000000000003e4000000000000044400000000000004440"); + var geo = new LineString(new List { new Point(new Coordinate(30, 10)), new Point(new Coordinate(10, 30)), new Point(new Coordinate(40, 40)) }); // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + byte[] bytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + Geometry geoReconstituted = WkbConverter.FromBinary(bytes); // ASSERT - Assert.Equal(bytes, newBytes); + Assert.True(geo.Equals(geoReconstituted), "Geometries are not equivalent."); } [Fact] @@ -240,17 +106,16 @@ public void LineStringTestWithDoubles_ToBinary_BigEndian() // ARRANGE // LINESTRING(30.1234 10.6, 10.77 30.85, 40.1 40.2, 21 07, 19 77) - byte[] bytes = HexStringToByteArray("000000000200000005403E1F972474538F402533333333333340258A3D70A3D70A403ED9999999999A40440CCCCCCCCCCD404419999999999A4035000000000000401C00000000000040330000000000004053400000000000"); + var geo = new LineString(new List { new Point(new Coordinate(30, 10)), new Point(new Coordinate(10, 30)), new Point(new Coordinate(40, 40)) }); // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + byte[] bytes = WkbConverter.ToBinary(geo, Endianness.BIG); + Geometry geoReconstituted = WkbConverter.FromBinary(bytes); // ASSERT - Assert.Equal(bytes, newBytes); + Assert.True(geo.Equals(geoReconstituted), "Geometries are not equivalent."); } - #endregion #region MultiLineString Tests @@ -267,10 +132,10 @@ public void MultiLineStringTest_FromBinary_BigEndian() // ASSERT MultiLineString lineString = Assert.IsType(geo); - Assert.Equal(2, lineString.Coordinates.Count()); - LineString ls1 = Assert.IsType(lineString.Coordinates.ElementAt(1)); - Assert.Equal(40, ls1.Coordinates.ElementAt(2).Longitude); - Assert.Equal(20, ls1.Coordinates.ElementAt(2).Latitude); + Assert.Equal(2, lineString.LineStrings.Count()); + LineString ls1 = Assert.IsType(lineString.LineStrings.ElementAt(1)); + Assert.Equal(40, ls1.LineSegments.ElementAt(2).ElementAt(0).Coordinates.Longitude); + Assert.Equal(20, ls1.LineSegments.ElementAt(2).ElementAt(0).Coordinates.Latitude); } [Fact] @@ -286,10 +151,10 @@ public void MultiLineStringTest_FromBinary_LittleEndian() // ASSERT MultiLineString lineString = Assert.IsType(geo); - Assert.Equal(2, lineString.Coordinates.Count()); - LineString ls1 = Assert.IsType(lineString.Coordinates.ElementAt(1)); - Assert.Equal(40, ls1.Coordinates.ElementAt(2).Longitude); - Assert.Equal(20, ls1.Coordinates.ElementAt(2).Latitude); + Assert.Equal(2, lineString.LineStrings.Count()); + LineString ls1 = Assert.IsType(lineString.LineStrings.ElementAt(1)); + Assert.Equal(40, ls1.LineSegments.ElementAt(2).ElementAt(0).Coordinates.Longitude); + Assert.Equal(20, ls1.LineSegments.ElementAt(2).ElementAt(0).Coordinates.Latitude); } [Fact] @@ -297,11 +162,12 @@ public void MultiLineStringTest_ToBinary_BigEndian() { // ARRANGE - // MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10)) - byte[] bytes = HexStringToByteArray("00000000050000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403E000000000000403E00000000000040440000000000004034000000000000403E0000000000004024000000000000"); + // MULTILINESTRING((10 10,20 20,10 40),(40 40,30 30,40 20,30 10)) + byte[] bytes = HexStringToByteArray("0000000005000000020000000002000000044024000000000000402400000000000040340000000000004034000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000640440000000000004044000000000000403E000000000000403E000000000000403E000000000000403E0000000000004044000000000000403400000000000040440000000000004034000000000000403E0000000000004024000000000000"); // ACT Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); // ASSERT @@ -357,8 +223,8 @@ public void MultiPointTest_FromBinary_BigEndian() // ASSERT MultiPoint mp = Assert.IsType(geo); - Assert.Equal(21.06, mp.Coordinates.ElementAt(0).Longitude); - Assert.Equal(19.77, mp.Coordinates.ElementAt(0).Latitude); + Assert.Equal(21.06, mp.Points.ElementAt(0).GetLongitude()); + Assert.Equal(19.77, mp.Points.ElementAt(0).GetLatitude()); } [Fact] @@ -527,101 +393,6 @@ public void PolygonTest_ToBinary_BigEndian() #endregion - #region GeometryCollection Tests - [Fact] - public void GeometryCollectionTest_FromBinary_BigEndian() - { - // ARRANGE - - // GEOMETRYCOLLECTION(POINT (40 10),LINESTRING(10 10, 20 20, 10 40),POLYGON((40 40, 20 45, 45 30, 40 40))) - byte[] bytes = HexStringToByteArray("0000000007000000030000000001404400000000000040240000000000000000000002000000034024000000000000402400000000000040340000000000004034000000000000402400000000000040440000000000000000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403E00000000000040440000000000004044000000000000"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - - // ASSERT - GeometryCollection geoCollection = Assert.IsType(geo); - Point point = Assert.IsType(geoCollection.Geometries.ElementAt(0)); - LineString lineString = Assert.IsType(geoCollection.Geometries.ElementAt(1)); - Polygon polygon = Assert.IsType(geoCollection.Geometries.ElementAt(2)); - Assert.Equal(40, point.Coordinates.Longitude); - Assert.Equal(10, point.Coordinates.Latitude); - } - - [Fact] - public void GeometryCollectionTest_FromBinary_LittleEndian() - { - // ARRANGE - - // GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) - byte[] bytes = HexStringToByteArray("010700000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - - // ASSERT - GeometryCollection geoCollection = Assert.IsType(geo); - Point point = Assert.IsType(geoCollection.Geometries.ElementAt(0)); - LineString lineString = Assert.IsType(geoCollection.Geometries.ElementAt(1)); - Assert.Equal(4, point.Coordinates.Longitude); - Assert.Equal(6, point.Coordinates.Latitude); - } - - [Fact] - public void GeometryCollectionTest_ToBinary_LittleEndian() - { - // ARRANGE - - // GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) - byte[] bytes = HexStringToByteArray("010700000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); - - // ASSERT - Assert.Equal(bytes, newBytes); - } - - [Fact] - public void GeometryCollectionTest_ToBinary_BigEndian() - { - // ARRANGE - - // GEOMETRYCOLLECTION(POINT (40 10),LINESTRING(10 10, 20 20, 10 40),POLYGON((40 40, 20 45, 45 30, 40 40))) - byte[] bytes = HexStringToByteArray("0000000007000000030000000001404400000000000040240000000000000000000002000000034024000000000000402400000000000040340000000000004034000000000000402400000000000040440000000000000000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403E00000000000040440000000000004044000000000000"); - - // ACT - Geometry geo = WkbConverter.FromBinary(bytes); - byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); - - // ASSERT - Assert.Equal(bytes, newBytes); - } - - #endregion - - #region Private Methods - - private static byte[] HexStringToByteArray(string hexString) - { - if (hexString.Length % 2 != 0) - { - throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", hexString)); - } - - byte[] data = new byte[hexString.Length / 2]; - - for (int index = 0; index < data.Length; index++) - { - string byteValue = hexString.Substring(index * 2, 2); - data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - } - - return data; - } - - #endregion } } diff --git a/GeoJSON.Tests/geometrycollection.json b/GeoJSON.Tests/geometrycollection.json deleted file mode 100644 index 7870c83..0000000 --- a/GeoJSON.Tests/geometrycollection.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "type": "GeometryCollection", - "geometries": [ - { - "type": "LineString", - "coordinates": [ - [ 102.0, 0.0 ], - [ 103.0, 1.0 ], - [ 104.0, 0.0 ], - [ 105.0, 1.0 ] - ] - }, - { - "type": "Polygon", - "coordinates": [ - [ - [ -10.0, -10.0 ], - [ 10.0, -10.0 ], - [ 10.0, 10.0 ], - [ -10.0, -10.0 ] - ] - ] - }, - { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ 180.0, 40.0 ], - [ 180.0, 50.0 ], - [ 170.0, 50.0 ], - [ 170.0, 40.0 ], - [ 180.0, 40.0 ] - ] - ], - [ - [ - [ -170.0, 40.0 ], - [ -170.0, 50.0 ], - [ -180.0, 50.0 ], - [ -180.0, 40.0 ], - [ -170.0, 40.0 ] - ] - ] - ] - } - ] -} \ No newline at end of file diff --git a/GeoJSON.Tests/multilinestring.json b/GeoJSON.Tests/multilinestring.json index c998174..9dbd4e2 100644 --- a/GeoJSON.Tests/multilinestring.json +++ b/GeoJSON.Tests/multilinestring.json @@ -1,13 +1,169 @@ -{ +{ "type": "MultiLineString", "coordinates": [ [ - [ 170.0, 45.0 ], - [ 180.0, 45.0 ] + [ + 0.0, + 0.0 + ], + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ], + [ + 1.0, + 1.0 + ], + [ + 0.0, + 0.0 + ], + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ], + [ + 1.0, + 1.0 + ], + [ + 0.0, + 0.0 + ], + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ], + [ + 1.0, + 1.0 + ], + [ + 0.0, + 0.0 + ], + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ], + [ + 1.0, + 1.0 + ] ], [ - [ -180.0, 45.0 ], - [ -170.0, 45.0 ] + [ + 5.0, + 0.0 + ], + [ + 5.0, + 2.0 + ], + [ + 5.0, + 0.0 + ], + [ + 5.0, + 3.0 + ], + [ + 6.0, + 0.0 + ], + [ + 6.0, + 2.0 + ], + [ + 6.0, + 0.0 + ], + [ + 6.0, + 3.0 + ], + [ + 5.0, + 0.0 + ], + [ + 5.0, + 2.0 + ], + [ + 5.0, + 0.0 + ], + [ + 5.0, + 3.0 + ], + [ + 6.0, + 0.0 + ], + [ + 6.0, + 2.0 + ], + [ + 6.0, + 0.0 + ], + [ + 6.0, + 3.0 + ], + [ + 5.0, + 0.0 + ], + [ + 5.0, + 2.0 + ], + [ + 5.0, + 0.0 + ], + [ + 5.0, + 3.0 + ], + [ + 6.0, + 0.0 + ], + [ + 6.0, + 2.0 + ], + [ + 6.0, + 0.0 + ], + [ + 6.0, + 3.0 + ] ] ] } \ No newline at end of file diff --git a/GeoJSON.Tests/polygon.json b/GeoJSON.Tests/polygon.json deleted file mode 100644 index 308ba3e..0000000 --- a/GeoJSON.Tests/polygon.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "type": "Polygon", - "coordinates": [ - [ - [ -10.0, -10.0 ], - [ 10.0, -10.0 ], - [ 10.0, 10.0 ], - [ -10.0, -10.0 ] - ] - ] -} \ No newline at end of file diff --git a/GeoJSON.sln b/GeoJSON.sln index 3ae0fd1..00cb0ad 100644 --- a/GeoJSON.sln +++ b/GeoJSON.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34003.232 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeoJSON", "GeoJSON\GeoJSON.csproj", "{13B19F69-F3B6-497E-A9E0-D6DFCC533EC6}" EndProject @@ -9,9 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeoJSON.Tests", "GeoJSON.Te EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D5ED7550-3703-4250-B3F5-B3DFB135A6A5}" ProjectSection(SolutionItems) = preProject - README.md = README.md + .editorconfig = .editorconfig EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensions", "Extensions\Extensions.csproj", "{AA7705B8-F518-41C2-8E72-5E22962C7096}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,6 +28,10 @@ Global {457C0EFB-99D9-46DB-8949-B5B90A855847}.Debug|Any CPU.Build.0 = Debug|Any CPU {457C0EFB-99D9-46DB-8949-B5B90A855847}.Release|Any CPU.ActiveCfg = Release|Any CPU {457C0EFB-99D9-46DB-8949-B5B90A855847}.Release|Any CPU.Build.0 = Release|Any CPU + {AA7705B8-F518-41C2-8E72-5E22962C7096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA7705B8-F518-41C2-8E72-5E22962C7096}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA7705B8-F518-41C2-8E72-5E22962C7096}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA7705B8-F518-41C2-8E72-5E22962C7096}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GeoJSON/Feature.cs b/GeoJSON/Feature.cs index ca7fa78..981a49a 100644 --- a/GeoJSON/Feature.cs +++ b/GeoJSON/Feature.cs @@ -33,6 +33,7 @@ public class Feature : GeoJson [JsonProperty(PropertyName = "id", NullValueHandling = NullValueHandling.Ignore)] public FeatureId Id { get; } + #endregion #region Constructors @@ -43,11 +44,12 @@ public class Feature : GeoJson /// The geometry to create the feature from /// The feature properties [JsonConstructor] - public Feature(Geometry geometry, IDictionary properties = null, IEnumerable boundingBox = null, FeatureId id = null) : base(GeoJsonType.Feature, geometry == null ? false : geometry.IsThreeDimensional(), boundingBox) + public Feature(Geometry geometry, IDictionary properties = null, FeatureId id = null) : base(GeoJsonType.Feature, geometry != null && geometry.IsThreeDimensional()) { this.Geometry = geometry; // Geometry can be null this.Properties = properties ?? new Dictionary(); this.Id = id; // id can be null + } #endregion @@ -87,13 +89,13 @@ public override bool Equals(object obj) bool bBoxEqual = true; - if (this.BoundingBox != null && other.BoundingBox != null) + if (this?.Geometry?.BoundingBox != null && other?.Geometry?.BoundingBox != null) { - bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); + bBoxEqual = this.Geometry.BoundingBox.SequenceEqual(other.Geometry.BoundingBox); } else { - bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); + bBoxEqual = (this?.Geometry?.BoundingBox == null && other?.Geometry?.BoundingBox == null); } bool propertiesEqual = true; @@ -109,14 +111,14 @@ public override bool Equals(object obj) return this.Type == other.Type && - this.Geometry == other.Geometry && + this?.Geometry == other?.Geometry && bBoxEqual && propertiesEqual; } public override int GetHashCode() { - return Hashing.Hash(this.Type, this.Geometry, this.BoundingBox, this.Properties); + return Hashing.Hash(this.Type, this.Geometry, this.Geometry.BoundingBox, this.Properties); } public static bool operator ==(Feature left, Feature right) diff --git a/GeoJSON/FeatureCollection.cs b/GeoJSON/FeatureCollection.cs index cd4671f..338ceee 100644 --- a/GeoJSON/FeatureCollection.cs +++ b/GeoJSON/FeatureCollection.cs @@ -1,6 +1,7 @@ using BAMCIS.GeoJSON.Serde; using Newtonsoft.Json; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -9,8 +10,8 @@ namespace BAMCIS.GeoJSON /// /// Represents a collection of feature objects /// - [JsonConverter(typeof(InheritanceBlockerConverter))] - public class FeatureCollection : GeoJson + [JsonConverter(typeof(FeatureCollectionConverter))] + public class FeatureCollection : GeoJson, IEnumerable { #region Public Properties @@ -20,6 +21,11 @@ public class FeatureCollection : GeoJson [JsonProperty(PropertyName = "features")] public IEnumerable Features { get; } + + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public Rectangle BoundingBox { get; private set; } + #endregion #region Constructors @@ -29,20 +35,77 @@ public class FeatureCollection : GeoJson /// /// The features that are part of the feature collection [JsonConstructor] - public FeatureCollection(IEnumerable features, IEnumerable boundingBox = null) : base(GeoJsonType.FeatureCollection, features.Any(x => x.IsThreeDimensional()), boundingBox) + public FeatureCollection(IEnumerable features) : base(GeoJsonType.FeatureCollection, features.Any(x => x.IsThreeDimensional())) { - this.Features = features ?? throw new ArgumentNullException("features"); + this.Features = features ?? throw new ArgumentNullException(nameof(features)); + + this.BoundingBox = FetchBoundingBox(); + + } + + public Rectangle FetchBoundingBox() + { + double MaxLatitude = double.MinValue; + double MaxLongitude = double.MinValue; + double MinLatitude = double.MaxValue; + double MinLongitude = double.MaxValue; + + foreach (Feature feature in this.Features) + { + + if (feature.Geometry?.BoundingBox != null) + { + if (MaxLatitude < (feature?.Geometry?.BoundingBox?.MaxLatitude ?? 0.0)) + { + MaxLatitude = (double) ( feature?.Geometry?.BoundingBox.MaxLatitude ); + } + + if (MaxLongitude < (feature?.Geometry?.BoundingBox?.MaxLongitude ?? 0.0 )) + { + MaxLongitude = (double)feature.Geometry?.BoundingBox.MaxLongitude; + } + + if (MinLatitude > (feature?.Geometry?.BoundingBox?.MinLatitude ?? 0.0 )) + { + MinLatitude = (double) feature?.Geometry.BoundingBox.MinLatitude; + } + + if (MinLongitude > (feature?.Geometry?.BoundingBox?.MinLongitude ?? 0.0 )) + { + MinLongitude = (double) feature?.Geometry?.BoundingBox.MinLongitude; + } + } + else + { + MaxLatitude = 0; + MaxLongitude = 0; + MinLatitude = 0; + MinLongitude = 0; + } + } + + var LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + var LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + var UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + var UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); } #endregion #region Public Methods + #region Conversion Methods public new static FeatureCollection FromJson(string json) { return JsonConvert.DeserializeObject(json); } + #endregion Conversion Methods + + #region Equality Operations + public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) @@ -109,6 +172,24 @@ public override int GetHashCode() return !(left == right); } - #endregion + #endregion Equality Operations + + #endregion Public Methods + + #region Enumerable + public IEnumerator GetEnumerator() + { + foreach (Feature feat in this.Features) + { + yield return feat; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion Enumerable } } diff --git a/GeoJSON/GeoJSON.csproj b/GeoJSON/GeoJSON.csproj index 4600b0c..4a43a39 100644 --- a/GeoJSON/GeoJSON.csproj +++ b/GeoJSON/GeoJSON.csproj @@ -1,7 +1,6 @@  - - netstandard1.6;netstandard2.0;net45 + net7.0 1.6 BAMCIS.GeoJSON 2.3.1 @@ -11,7 +10,8 @@ bamcis.io true true - + + https://github.com/bamcis-io/GeoJSON https://github.com/bamcis-io/GeoJSON Git @@ -20,21 +20,18 @@ true GeoJSON.snk false - + + MIT - true true snupkg - - - + + - - + - - + \ No newline at end of file diff --git a/GeoJSON/GeoJson.cs b/GeoJSON/GeoJson.cs index 155034c..452b2cc 100644 --- a/GeoJSON/GeoJson.cs +++ b/GeoJSON/GeoJson.cs @@ -7,6 +7,8 @@ namespace BAMCIS.GeoJSON { + + /// /// A base abstract class for the implementation of GeoJson /// @@ -17,7 +19,7 @@ public abstract class GeoJson private static readonly Dictionary typeToDerivedType; private static readonly Dictionary derivedTypeToType; - private bool is3D; + private readonly bool is3D; #endregion @@ -29,19 +31,6 @@ public abstract class GeoJson [JsonProperty(PropertyName = "type")] public GeoJsonType Type { get; } - /// - /// A GeoJSON object MAY have a member named "bbox" to include - /// information on the coordinate range for its Geometries, Features, or - /// FeatureCollections.The value of the bbox member MUST be an array of - /// length 2*n where n is the number of dimensions represented in the - /// contained geometries, with all axes of the most southwesterly point - /// followed by all axes of the more northeasterly point. The axes order - /// of a bbox follows the axes order of geometries. - /// - /// Takes the form [west, south, east, north] for 2D or of the form [west, south, min-altitude, east, north, max-altitude] for 3D - /// - [JsonProperty(PropertyName = "bbox", NullValueHandling = NullValueHandling.Ignore)] - public IEnumerable BoundingBox { get; } #endregion @@ -54,6 +43,7 @@ static GeoJson() { typeToDerivedType = new Dictionary() { + { typeof(LineSegment), GeoJsonType.LineSegment }, { typeof(LineString), GeoJsonType.LineString }, { typeof(MultiLineString), GeoJsonType.MultiLineString }, { typeof(MultiPoint), GeoJsonType.MultiPoint }, @@ -72,25 +62,10 @@ static GeoJson() /// Base constructor that all derived classes must implement /// /// The type of the GeoJson object - protected GeoJson(GeoJsonType type, bool is3D, IEnumerable boundingBox = null) + protected GeoJson(GeoJsonType type, bool is3D) { this.Type = type; - this.BoundingBox = boundingBox; this.is3D = is3D; - - if (this.BoundingBox != null) - { - int length = boundingBox.Count(); - - if (this.is3D && length != 6) - { - throw new ArgumentOutOfRangeException("boundingBox", "The bounding box must contain 6 elements for a 3D GeoJSON object."); - } - else if (!this.is3D && length != 4) - { - throw new ArgumentOutOfRangeException("boundingBox", "The bounding box must contain 4 elements for a 2D GeoJSON object."); - } - } } #endregion diff --git a/GeoJSON/GeoJsonType.cs b/GeoJSON/GeoJsonType.cs index d7ed40d..f35fa1a 100644 --- a/GeoJSON/GeoJsonType.cs +++ b/GeoJSON/GeoJsonType.cs @@ -12,6 +12,10 @@ public enum GeoJsonType LineString, + LineSegment, + + LinearRing, + MultiLineString, Polygon, diff --git a/GeoJSON/Geometry.cs b/GeoJSON/Geometry.cs index 003a131..6bac4de 100644 --- a/GeoJSON/Geometry.cs +++ b/GeoJSON/Geometry.cs @@ -5,19 +5,30 @@ using System.Collections.Generic; using System.Linq; + + namespace BAMCIS.GeoJSON { + + /// /// A base abstract class for geometry types /// [JsonConverter(typeof(GeometryConverter))] - public abstract class Geometry : GeoJson + public abstract class Geometry: GeoJson { #region Private Fields private static readonly Dictionary typeToDerivedType; private static readonly Dictionary derivedTypeToType; + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public abstract Rectangle BoundingBox { get; } + + [JsonIgnore] + internal Rectangle _BoundingBox { get; set; } + #endregion #region Constructors @@ -29,7 +40,9 @@ static Geometry() { typeToDerivedType = new Dictionary() { + { typeof(LineSegment), GeoJsonType.LineSegment }, { typeof(LineString), GeoJsonType.LineString }, + { typeof(LinearRing), GeoJsonType.LinearRing }, { typeof(MultiLineString), GeoJsonType.MultiLineString }, { typeof(MultiPoint), GeoJsonType.MultiPoint }, { typeof(MultiPolygon), GeoJsonType.MultiPolygon }, @@ -46,7 +59,42 @@ static Geometry() /// /// The GeoJson type [JsonConstructor] - protected Geometry(GeoJsonType type, bool is3D, IEnumerable boundingBox = null) : base(type, is3D, boundingBox) + protected Geometry(GeoJsonType type, bool is3D) : base(type, is3D) + { + ConstructorEvaluator(type); + } + + + /// + /// Converts angles from Radians to Degrees + /// + /// + /// + public static double RadiansToDegrees(double radians) + { + return ( radians * 180 / Math.PI ); + } + + /// + /// Converts angles from Degrees to Radians + /// + /// + /// + public static double DegreesToRadians(double radians) + { + return ( radians * 180 / Math.PI ); + } + + /// + /// Each inherited class must implement this constructor + /// + /// The GeoJson type + protected Geometry(IEnumerable geometries = null) : base(GeoJsonType.Feature, geometries.FirstOrDefault()?.IsThreeDimensional() ?? false) + { + + } + + protected static void ConstructorEvaluator(GeoJsonType type) { if (!derivedTypeToType.ContainsKey(type)) { @@ -58,9 +106,13 @@ protected Geometry(GeoJsonType type, bool is3D, IEnumerable boundingBox #region Public Methods + #region Converters + public static new Geometry FromJson(string json) { - return JsonConvert.DeserializeObject(json); + var geometry = JsonConvert.DeserializeObject(json); + + return geometry; } /// @@ -93,6 +145,8 @@ public byte[] ToWkb(Endianness endianness = Endianness.LITTLE) return WkbConverter.ToBinary(this, endianness); } + + /// /// Gets the appropriate class type corresponding to the enum /// representing the type @@ -111,6 +165,10 @@ public byte[] ToWkb(Endianness endianness = Endianness.LITTLE) } } + #endregion Converters + + #region Equality Evaluators + public static bool operator ==(Geometry left, Geometry right) { if (ReferenceEquals(left, right)) @@ -135,6 +193,10 @@ public byte[] ToWkb(Endianness endianness = Endianness.LITTLE) public abstract override int GetHashCode(); + + #endregion Equality Evaluators + #endregion } + } diff --git a/GeoJSON/GeometryCollection.cs b/GeoJSON/GeometryCollection.cs index 3ac1af4..6c42a0a 100644 --- a/GeoJSON/GeometryCollection.cs +++ b/GeoJSON/GeometryCollection.cs @@ -23,7 +23,11 @@ public class GeometryCollection : Geometry [JsonProperty(PropertyName = "geometries")] public IEnumerable Geometries { get; } - #endregion + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox { get; } + + #endregion Public Properties #region Constructors @@ -32,20 +36,29 @@ public class GeometryCollection : Geometry /// /// The geometries that are part of the collection [JsonConstructor] - public GeometryCollection(IEnumerable geometries, IEnumerable boundingBox = null) : base(GeoJsonType.GeometryCollection, geometries.Any(x => x.IsThreeDimensional()), boundingBox) + public GeometryCollection(IEnumerable geometries) : base(GeoJsonType.GeometryCollection, geometries.Any(x => x.IsThreeDimensional())) { - this.Geometries = geometries ?? throw new ArgumentNullException("geometries"); + this.Geometries = geometries.ToList() ?? throw new ArgumentNullException("geometries"); + + this.BoundingBox = FetchBoundingBox(); } #endregion #region Public Methods + #region Converters + public new static GeometryCollection FromJson(string json) { return JsonConvert.DeserializeObject(json); } + + #endregion Converters + + #region Equality Evaluators + public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) @@ -112,6 +125,68 @@ public override int GetHashCode() return !(left == right); } + #endregion Equality Evaluators + + #region Topological Operations + + public Rectangle FetchBoundingBox() + { + double MaxLatitude = double.MinValue; + double MaxLongitude = double.MinValue; + double MinLatitude = double.MaxValue; + double MinLongitude = double.MaxValue; + bool allGeomsHaveEmptyBoundingBox = true; + foreach (var geometry in this.Geometries) + { + var bbox = geometry?.BoundingBox ?? null; + + if (bbox != null) + { + allGeomsHaveEmptyBoundingBox = false; + if (MaxLatitude < bbox.MaxLatitude) + { + MaxLatitude = bbox.MaxLatitude; + } + + if (MaxLongitude < bbox.MaxLongitude) + { + MaxLongitude = bbox.MaxLongitude; + } + + if (MinLatitude > bbox.MinLatitude) + { + MinLatitude = bbox.MinLatitude; + } + + if (MinLongitude > bbox.MinLongitude) + { + MinLongitude = bbox.MinLongitude; + } + } + + } + + if (allGeomsHaveEmptyBoundingBox) + { + return null; + } + else + { + Point LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + Point LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + Point UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + Point UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); + + } + + } + + + #endregion Topological Operations + + #endregion } } diff --git a/GeoJSON/GeometryEnumerator.cs b/GeoJSON/GeometryEnumerator.cs new file mode 100644 index 0000000..c9d2bba --- /dev/null +++ b/GeoJSON/GeometryEnumerator.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace BAMCIS.GeoJSON +{ + public class GeometryEnumerator : IEnumerator where T: Geometry + { + + public int Position { get; private set; } = 0; + + public T[] Geometries { get; private set; } + + public GeometryEnumerator(IEnumerable geometries) + { + Geometries = geometries.ToArray(); + } + + public T Current() + { + return this.Geometries[Position]; + } + + object IEnumerator.Current + { + get + { + return Current(); + } + } + + T IEnumerator.Current + { + get + { + return Current(); + } + } + + public void Dispose() + { + // Suppress finalization. + GC.SuppressFinalize(this); + + } + + public bool MoveNext() + { + this.Position ++; + + if (this.Position < Geometries.Count()) + { + return true; + } + else + { + return false; + } + } + + public void Reset() + { + this.Position = 0; + } + } +} \ No newline at end of file diff --git a/GeoJSON/GlobalSuppressions.cs b/GeoJSON/GlobalSuppressions.cs new file mode 100644 index 0000000..a9d7852 --- /dev/null +++ b/GeoJSON/GlobalSuppressions.cs @@ -0,0 +1,16 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0059:Atribuição desnecessária de um valor", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON.FeatureCollection.Equals(System.Object)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0059:Atribuição desnecessária de um valor", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON.GeometryCollection.Equals(System.Object)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0059:Atribuição desnecessária de um valor", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON.Polygon.Touches(BAMCIS.GeoJSON.Point,System.Double)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0037:Usar o nome do membro inferido", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON.Serde.PolygonConverter.WriteJson(Newtonsoft.Json.JsonWriter,System.Object,Newtonsoft.Json.JsonSerializer)")] +[assembly: SuppressMessage("CodeQuality", "IDE0051:Remover membros privados não utilizados", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON.Point.Copy~BAMCIS.GeoJSON.Point")] +[assembly: SuppressMessage("Usage", "CA2208:Instanciar exceções de argumentos corretamente", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON.Point.#ctor(System.Double,System.Double,System.Double)")] +[assembly: SuppressMessage("Style", "IDE0090:Usar 'new(...)'", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Justification = "Test methods require underscores for readability.", Scope = "namespaceanddescendants", Target = "~M:BAMCIS.GeoJSON")] +[assembly: SuppressMessage("Style", "IDE0016:Use a expressão 'throw'", Justification = "internal Policy", Scope = "member", Target = "~M:BAMCIS.GeoJSON.MultiPoint.#ctor(System.Collections.Generic.IEnumerable{BAMCIS.GeoJSON.Point})")] diff --git a/GeoJSON/ITopology.cs b/GeoJSON/ITopology.cs new file mode 100644 index 0000000..db43e94 --- /dev/null +++ b/GeoJSON/ITopology.cs @@ -0,0 +1,44 @@ +using System; + +namespace BAMCIS.GeoJSON +{ + public interface ITouch + { + bool Touches(T other, double eps = double.MinValue * 100); + } + + public interface IIntersects + { + bool Intersects(T other, double eps = double.MinValue * 100); + } + + public interface IWithin + { + bool Within(T other, double eps = double.MinValue * 100); + } + + public interface IContains + { + bool Contains(T other, double eps = double.MinValue * 100); + } + + public interface IAdimTopology: IEquatable, ITouch, IWithin + { + + + } + + public interface ILine: IEquatable, IIntersects, ITouch, IWithin + { + + } + + + + public interface ITopology: ILine, IContains + { + + } + + public interface IPolygon : ITouch, IContains, IIntersects { } +} \ No newline at end of file diff --git a/GeoJSON/LineSegment.cs b/GeoJSON/LineSegment.cs new file mode 100644 index 0000000..4bbd432 --- /dev/null +++ b/GeoJSON/LineSegment.cs @@ -0,0 +1,723 @@ +using BAMCIS.GeoJSON.Serde; +using Extensions.ArrayExtensions; +using Extensions.ListExtensions; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace BAMCIS.GeoJSON +{ + /// + /// A LineSegment is composed solely of two points: + /// 1) an origin Point (p1) + /// + /// 2) a terminal Point (p2) + /// + [JsonConverter(typeof(InheritanceBlockerConverter))] + public class LineSegment: Geometry, + ILine, + ILine, + ILine, + ILine, + IEquatable, + IComparable, + IComparer, + IEnumerable, + IEnumerable + + { + #region Properties + + public IEnumerable Points { + + get + { + return new Point[2] { P1, P2 }; + } + } + + [JsonProperty(PropertyName = "Initial Point")] + public Point P1 { get; set; } + + [JsonProperty(PropertyName = "Final Point")] + public Point P2 { get; set; } + + [JsonProperty(PropertyName = "Length")] + public double Length { get; set; } + + public double MinLongitude { + + get { + return Math.Min(P1.GetLongitude(), P2.GetLongitude()); + } + } + + public double MaxLongitude + { + + get + { + return Math.Max(P1.GetLongitude(), P2.GetLongitude()); + } + } + + + public double MinLatitude + { + + get + { + return Math.Min(P1.GetLatitude(), P2.GetLatitude()); + } + } + + public double MaxLatitude + { + + get + { + return Math.Max(P1.GetLatitude(), P2.GetLatitude()); + } + } + + [JsonProperty(PropertyName = "BoundingBox")] + public override Rectangle BoundingBox + { + get + { + + if (this._BoundingBox == null) + { + this._BoundingBox = FetchBoundingBox(); + } + return this._BoundingBox; + } + } + + /// + /// Angle in radians of this lineSegment + /// + public double Angle { get; } + + + #endregion Properties + + + #region Constructors + [JsonConstructor] + public LineSegment(IEnumerable p1p2) : base(GeoJsonType.LineSegment, p1p2.First().Coordinates.HasElevation()) + { + + var P1P2List = p1p2.ToList(); + + if (P1P2List.Count != 2) + { + throw new ArgumentException($"{nameof(LineSegment)} only accepts an IEnumerable of size two"); + } + + this.P1 = P1P2List[0]; + + this.P2 = P1P2List[1]; + + + var dx = ( P1P2List[0].GetLongitude() - P1P2List[1].GetLongitude() ); + + var dy = ( P1P2List[0].GetLatitude() - P1P2List[1].GetLatitude() ); + + this.Length = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)); + + this.Angle = Math.Atan2(dy, dx); + + } + + public LineSegment(IEnumerable coordinates) : this(coordinates.Select(c => c.ToPoint())) + { + + } + + + public LineSegment(Point p1, Point p2) : base(GeoJsonType.LineSegment, p1.Coordinates.HasElevation()) + { + + this.P1 = p1; + + this.P2 = p2; + + + var dx = ( p1.GetLongitude() - p2.GetLongitude() ); + + var dy = ( p1.GetLatitude() - p2.GetLatitude() ); + + this.Length = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)); + + this.Angle = Math.Atan2(dy, dx); + + } + + + public Rectangle FetchBoundingBox() + { + var LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + + var LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + + var UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + + var UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); + } + + + #endregion Constructors + + #region Methods + + #region Private Methods + + + + + #endregion Private Methods + + + #region Public Methods + + #region Comparables + + public bool Equals(Point other) + { + return false; + } + + + public bool Equals(LineSegment other) + { + return (this.P1.Equals(other.P1) && + this.P2.Equals(other.P2) + ); + } + + public bool Equals(LinearRing other) + { + if (other.LineSegments.Count() > 1) + { + return false; + } + else + { + return this.Equals(other.LineSegments.First()); + } + } + + public bool Equals(LineString other) + { + if (other.LineSegments.Count() > 1) + { + return false; + } + else + { + return this.CompareTo(other.LineSegments.First()) == 0; + } + } + + public bool Equals(Polygon other) + { + return false; + } + + public override bool Equals(object obj) + { + var objCasted = obj as LineSegment; + + + if (ReferenceEquals(this, objCasted)) + { + return true; + } + + if (objCasted is null) + { + return false; + } + + return this.Equals(objCasted); + } + + public int CompareTo(LineSegment other) + { + if (this.Length < other.Length) + { + return -1; + } + else if (this.Length > other.Length) + { + return 1; + } + else{ return 0; } + } + + public int Compare(LineSegment x, LineSegment y) + { + return x.CompareTo(y); + } + + public override int GetHashCode() + { + return Tuple.Create(P1.GetHashCode(), P2.GetHashCode()).GetHashCode(); + } + + public static bool operator ==(LineSegment left, LineSegment right) + { + if (left is null) + { + return right is null; + } + + return left.P1 == right.P1 && left.P2 == right.P2; + } + + public static bool operator !=(LineSegment left, LineSegment right) + { + return !( left == right ); + } + + public static bool operator <(LineSegment left, LineSegment right) + { + return left.Length < right.Length; + } + + public static bool operator <=(LineSegment left, LineSegment right) + { + return left is null || left.CompareTo(right) <= 0; + } + + public static bool operator >(LineSegment left, LineSegment right) + { + return left is not null && left.CompareTo(right) > 0; + } + + public static bool operator >=(LineSegment left, LineSegment right) + { + return left is null ? right is null : left.CompareTo(right) >= 0; + } + + #endregion Comparables + + #region Enumerators + + public IEnumerator GetEnumerator() + { + foreach (Point point in Points) + { + yield return point; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + + #endregion Enumerators + + #region Topological Operations + + internal bool HasElevation() + { + return this.Points.Any(p => p.Coordinates.HasElevation()); + } + public bool Within(Polygon otherGeometry) + { + return this.Points.All(p => otherGeometry.Contains(p)); + } + + public bool Intersects(Polygon otherGeometry) + { + return this.Points.All(p => otherGeometry.Contains(p)); + } + + /// + /// The four endpoints are: + /// P1) (x0, y0) + /// P2) (x1,y1) + /// P3) (a0,b0) + /// P4) (a1,b1) + /// + /// The returned values xy and ab are the fractional distance along xy and ab + /// and are only defined when the result is true + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static bool FindIntersection(double x0, double y0, + double x1, double y1, + double a0, double b0, + double a1, double b1) + { + + double xy; + double ab; + + bool partial = false; + double denom = ( b0 - b1 ) * ( x0 - x1 ) - ( y0 - y1 ) * ( a0 - a1 ); + if (denom == 0) + { + xy = -1; + ab = -1; + + return false; + } + else + { + xy = ( a0 * ( y1 - b1 ) + a1 * ( b0 - y1 ) + x1 * ( b1 - b0 ) ) / denom; + + partial = IsBetween(0, xy, 1); + + if (partial) + { + // no point calculating this unless xy is between 0 & 1 + ab = ( y1 * ( x0 - a1 ) + b1 * ( x1 - x0 ) + y0 * ( a1 - x1 ) ) / denom; + + if (IsBetween(0, ab, 1)) + { + ab = 1 - ab; + xy = 1 - xy; + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + } + + + private static bool IsBetween(double x0, double x, double x1) + { + return ( x >= x0 ) && ( x <= x1 ); + } + + public bool WithinBoundaries(LineSegment otherLineSegment) + { + return this.BoundingBox.WithinBoundaries(otherLineSegment.BoundingBox); + } + + /// + /// Verifies whether a point is aligned to this linesegment. + /// + /// This approach is done by means of the cross-product between the initial point of the LineSegment (p1) and the + /// provided unknown point (p3) and the final point of the LineSegment (p2) and the + /// + /// + /// + public bool IsAligned([NotNull] Point p3, double eps = double.MinValue * 100) + { + if (eps == double.NegativeInfinity) + { + eps = double.MinValue * 100; + } + + var p1p3LineSeg = new LineSegment(this.P1, p3); + + double k = Math.Abs(this.Angle - p1p3LineSeg.Angle); + + if (k == 0) + { + return true; + } + else if (k <= eps) + { + return true; + } + else + { + return false; + } + } + + public bool Intersects(Point other, double eps = double.MinValue * 100) + { + return false; + } + + /// + /// Verifies whether a point intersects to this linesegment. + /// + /// A point (p) intersects a lineSegment if (and only if) two conditions are met: + /// 1) the direction of the vector composed of the points p and any of the edges of the linesegment is equal to the direction of the lineSegment itself + /// 2) the coordinates of the point must be within the boundaries of the linesegment. + /// + /// + /// + public bool Touches(Point point, double eps = double.MinValue * 100) + { + if (IsAligned(point, eps)) + { + if (point.GetLatitude() < this.MinLatitude || + point.GetLatitude() > this.MaxLatitude || + point.GetLongitude() < this.MinLongitude || + point.GetLongitude() > this.MaxLongitude) + { + return false; + } + else + { + return true; + } + } + else + { + return false; + } + } + + public bool Within(Point other, double eps = double.MinValue * 100) + { + return false; + } + + + + public bool Intersects(LineSegment otherGeometry, double eps = double.MinValue * 100) + { + var angleDif = Math.Abs(otherGeometry.Angle - this.Angle); + + if (angleDif < eps) + { + return FindIntersection(this.P1.Coordinates.Longitude, this.P1.Coordinates.Latitude, + this.P2.Coordinates.Longitude, this.P2.Coordinates.Latitude, + otherGeometry.P1.Coordinates.Longitude, otherGeometry.P1.Coordinates.Latitude, + otherGeometry.P2.Coordinates.Longitude, otherGeometry.P2.Coordinates.Latitude + ); + } + else + { + return false; + } + } + + public bool Touches(LineSegment otherlineSegment, double eps = double.MinValue * 100) + { + + // Checking first if any of the points that compose both lineSegments are equal, and also verifying if the lines do not intersect each other. + // If so, then, the lineSegments do touch each other; + if (otherlineSegment.Intersects(this)) + { + return false; + } + else + { + if (otherlineSegment.Any(pOther => this.Equals(pOther))) + { + return true; + } + // In this second, case, we must verify if the lines touch, and, furthermore, if they do not intersects. + else + { + if (otherlineSegment.Any(pOther => this.Touches(pOther)) + ) + { + return true; + } + else + { + return false; + } + } + } + } + + public bool Within(LineSegment other, double eps = double.MinValue * 100) + { + return false; + } + + + + public bool Intersects(LinearRing other, double eps = double.MinValue * 100) + { + foreach(LineSegment lineSegmentFromOther in other) + { + if (this.Intersects(lineSegmentFromOther)) + { + return true; + } + } + return false; + } + + public bool Touches(LinearRing other, double eps = double.MinValue * 100) + { + foreach (LineSegment lineSegmentFromOther in other) + { + if (this.Touches(lineSegmentFromOther)) + { + return true; + } + } + + return false; + } + + public bool Within(LinearRing other, double eps = double.MinValue * 100) + { + return false; + } + + + + public bool Intersects(Polygon other, double eps = double.MinValue * 100) + { + return other.Intersects(this, eps); + } + + public bool Touches(Polygon other, double eps = double.MinValue * 100) + { + foreach(LinearRing linearRingFromOther in other) + { + if (this.Touches(linearRingFromOther)) + { + return true; + } + } + return false; + } + + public bool Within(Polygon other, double eps = double.MinValue * 100) + { + return other.Contains(this); + } + + + + + #endregion Topological Operations + + + #endregion Public Methods + + #region Converters + + public static LineString CoordinatesToLineString(IEnumerable coordinates) + { + List lineSegments = PositionsToLineSegments(coordinates); + + return new LineString(lineSegments); + } + + public static List PositionsToLineSegments(IEnumerable _positions) + { + var positions = _positions.ToList(); + var lineSegments = new List { }; + + var position_init = positions.Pop(); + + var NPositions = positions.Count; + + for (var i = 0; i < NPositions; i++) + { + + var position_final = positions.Pop(); + + var lineSegment = new LineSegment(new Point(position_init), + new Point(position_final) + ); + + lineSegments.Add(lineSegment); + + position_init = position_final; + + } + + return lineSegments; + } + + public static new LineSegment FromJson(string json) + { + return JsonConvert.DeserializeObject(json); + } + #endregion Converters + + + #endregion Methods + + } + + + + internal class LineSegmentEnumerator : IEnumerator, IDisposable + { + + #region Proprieties + public int Position { get; private set; } = 0; + + public List Coordinates { get; private set; } + + #endregion Proprieties + + public LineSegmentEnumerator(LineSegment lineSegment) + { + Coordinates = new List { lineSegment.P1, lineSegment.P2 }; + } + + public Point Current() + { + return this.Coordinates[Position]; + } + + object IEnumerator.Current + { + get + { + return Current(); + } + } + + + public void Dispose() + { + // Suppress finalization. + GC.SuppressFinalize(this); + + } + + public bool MoveNext() + { + this.Position++; + + if (this.Position < Coordinates.Count) + { + return true; + } + else + { + return false; + } + } + + public void Reset() + { + this.Position = 0; + } + } + +} diff --git a/GeoJSON/LineString.cs b/GeoJSON/LineString.cs index fe08c4f..ee963c1 100644 --- a/GeoJSON/LineString.cs +++ b/GeoJSON/LineString.cs @@ -1,6 +1,8 @@ using BAMCIS.GeoJSON.Serde; +using Extensions.ListExtensions; using Newtonsoft.Json; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -10,16 +12,55 @@ namespace BAMCIS.GeoJSON /// For type "LineString", the "coordinates" member is an array of two or /// more positions. /// - [JsonConverter(typeof(InheritanceBlockerConverter))] - public class LineString : Geometry + [JsonConverter(typeof(LineStringConverter))] + public class LineString : Geometry, + IEnumerable, + ILine, + ILine, + ILine, + ILine, + IContains, + IContains, + IContains, + IContains, + IEquatable, + IComparable, + IComparer + { #region Public Properties /// /// The coordinates of a linestring are an array of positions /// + [JsonProperty(PropertyName = "LineSegments")] + public IEnumerable LineSegments { get; } + [JsonProperty(PropertyName = "coordinates")] - public IEnumerable Coordinates { get; } + public IEnumerable Coordinates { get { return Points.Select(p => p.Coordinates).ToList(); } } + + [JsonIgnore] + public IEnumerable Points { get; init; } + + public double Length + { + get { + return LineSegments.Select(x => x.Length).Sum(); + } + } + + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox { + get { + + if (this._BoundingBox == null) + { + this._BoundingBox = FetchBoundingBox(this.Points); + } + return this._BoundingBox; + } + } #endregion @@ -29,25 +70,88 @@ public class LineString : Geometry /// Creates a new LineString /// /// The coordinates in the line string - public LineString(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.LineString, coordinates.Any(x => x.HasElevation()), boundingBox) + [JsonConstructor] + public LineString(IEnumerable lineSegments) : base(GeoJsonType.LineString, lineSegments.Any(x => x.HasElevation())) + { + this.Points = lineSegments.SelectMany(lineSeg => lineSeg.Points.ToList()).ToList(); + + this.LineSegments = lineSegments ?? throw new ArgumentNullException(nameof(lineSegments)); + + } + + + public LineString(IEnumerable points) : base(GeoJsonType.LineString, points.Any(x => x.HasElevation())) + { + this.Points = points; + + this.LineSegments = ConvertPointsToLineSegments(points.ToList()) ?? throw new ArgumentNullException("lineSegments"); + } + + public LineString(IEnumerable coordinates) : base(GeoJsonType.LineString, CoordinatesToPoints(coordinates).Any(x => x.HasElevation())) + { + this.Points = CoordinatesToPoints(coordinates); + + this.LineSegments = ConvertPointsToLineSegments(this.Points.ToList()) ?? throw new ArgumentNullException("lineSegments"); + } + + public static List CoordinatesToPoints(IEnumerable positions) + { + List points = positions.Select(x => new Point(x)).ToList(); + + return points; + + } + + private static IEnumerable ConvertPointsToLineSegments(List points) { - this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); + var lineSegments = new List(); + if (points.Count < 2) + { + throw new ArgumentException("lineString must have at least two points to define itself"); + } + + var previousPoint = points.Pop(); + + int NSize = points.Count; - if (this.Coordinates.Count() < 2) + for (int i=0; i < NSize; i++) { - throw new ArgumentOutOfRangeException("coordinates", "A LineString must have at least two positions."); + var nextPoint = points.Pop(); + + var lineSeg = new LineSegment(previousPoint, nextPoint); + + lineSegments.Add(lineSeg); + + previousPoint = nextPoint; + } + + return lineSegments; + } #endregion #region Public Methods + + #region Converters + + public LineSegment[] ToVector() + { + + return this.LineSegments.ToArray(); + } + public static new LineString FromJson(string json) { return JsonConvert.DeserializeObject(json); } + #endregion Converters + + #region Equality Evaluators + public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) @@ -73,15 +177,15 @@ public override bool Equals(object obj) bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); } - bool coordinatesEqual = true; + bool coordinatesEqual; - if (this.Coordinates != null && other.Coordinates != null) + if (this.LineSegments != null && other.LineSegments != null) { - coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); + coordinatesEqual = this.LineSegments.SequenceEqual(other.LineSegments); } else { - coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); + coordinatesEqual = (this.LineSegments == null && other.LineSegments == null); } return this.Type == other.Type && @@ -91,7 +195,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); + return Hashing.Hash(this.Type, this.LineSegments, this.BoundingBox); } public static bool operator ==(LineString left, LineString right) @@ -114,6 +218,395 @@ public override int GetHashCode() return !(left == right); } - #endregion + public int CompareTo(LineSegment other) + { + return this.Length.CompareTo(other.Length); + } + + public int Compare(LineSegment x, LineSegment y) + { + return x.Length.CompareTo(y.Length); + } + + + #endregion Equality Evaluators + + + #region Enumerable + public IEnumerator GetEnumerator() + { + foreach (LineSegment lineSegment in LineSegments) + { + yield return lineSegment; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new LineStringEnumerator(this); + } + + #endregion Enumerable + + + #region Topological Operations + + public static Rectangle FetchBoundingBox(IEnumerable points) + { + double MinLongitude = double.MaxValue; + + double MaxLongitude = double.MinValue; + + + double MinLatitude = double.MaxValue; + + + double MaxLatitude = double.MinValue; + + + foreach (var point in points) + { + if (MinLongitude > point.GetLongitude()) + { + MinLongitude = point.GetLongitude(); + } + + if (MaxLongitude < point.GetLongitude()) + { + MaxLongitude = point.GetLongitude(); + } + + if (MinLatitude > point.GetLatitude()) + { + MinLatitude = point.GetLatitude(); + } + + if (MaxLatitude < point.GetLatitude()) + { + MaxLatitude = point.GetLatitude(); + } + } + + var LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + + var LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + + var UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + + var UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); + } + + + public static bool Intersects(LineString left, LineString right, double eps = double.MinValue * 100) + { + foreach(var left_LineStringSegment in left.LineSegments) + { + foreach(var right_LineStringSegment in right.LineSegments) + { + + if (left_LineStringSegment.Intersects(right_LineStringSegment, eps)) + { + return true; + } + else + { + continue; + } + } + + } + + return false; + + } + + + public bool Intersects(LineString other, double eps = double.MinValue * 100) + { + return Intersects(this, other, eps); + } + + /// + /// Given a LineString and a point, this method evaluates if this point intersects any of the LineSegments that compose the provided LineString. + /// + /// If so, then this point is said to intersect this LineString. + /// + /// + /// + /// + /// + public static bool Intersects(LineString left, Point point, double eps = double.MinValue * 100) + { + + LineSegment[] lineSegments = left.LineSegments.ToArray(); + + foreach(var linesegment in left.LineSegments) + { + if (linesegment.Intersects(point, eps)) + { + return true; + } + else + { + continue; + } + } + + return false; + } + + public bool Within(Polygon otherGeometry) + { + return otherGeometry.Contains(this); + } + + public bool Intersects(LineString lineString) + { + foreach (LineSegment lineSegment in lineString) + { + if (this.Intersects(lineSegment)) + { + return true; + } + else + { + continue; + } + } + return false; + } + + + public bool Intersects(LineSegment other) + { + foreach (LineSegment lineSegment in this) + { + if (lineSegment.Intersects(other)) + { + return true; + } + else + { + continue; + } + } + return false; + } + + public bool Touches(LineString other, double eps = double.MinValue * 100) + { + + bool condition = false; + foreach(LineSegment lineSegmentFromOther in other) + { + foreach(var point in lineSegmentFromOther) + { + if (this.LineSegments.Any(line => line.Touches(point))) + { + condition = true; + } + } + } + + if (Intersects(other, eps)) + { + return false; + } + else + { + return condition; + } + } + + + public bool Equals(Point other) + { + return false; + } + + public bool Intersects(Point other, double eps = double.MinValue * 100) + { + return false; + } + + public bool Touches(Point other, double eps = double.MinValue * 100) + { + return other.Touches(this, eps); + } + + + + public bool Within(Point other, double eps = double.MinValue * 100) + { + return false; + } + + public bool Equals(LineSegment other) + { + if (this.LineSegments.Count() == 1) + { + return other.Equals(this); + } + else + { + return false; + } + } + + public bool Intersects(LineSegment other, double eps = double.MinValue * 100) + { + if (this.LineSegments.Count() == 1) + { + return this.LineSegments.First().Intersects(other, eps); + } + else + { + return false; + } + } + + public bool Touches(LineSegment other, double eps = double.MinValue * 100) + { + return this.LineSegments.Any(line => line.Touches(other, eps)); + } + + public bool Within(LineSegment other, double eps = double.MinValue * 100) + { + return false; + } + + public bool Equals(LinearRing other) + { + + var ls1 = this.LineSegments.ToList(); + var ls2 = this.LineSegments.ToList(); + + for (int i = 0; i < this.LineSegments.Count();i ++) + { + if (!ls1[i].Equals(ls2[i])) + { + return false; + } + } + + return true; + } + + public bool Intersects(LinearRing other, double eps = double.MinValue * 100) + { + return this.Intersects(other as LineString, eps); + } + + public bool Touches(LinearRing other, double eps = double.MinValue * 100) + { + return this.Touches(other as LineString, eps); + } + + public bool Within(LinearRing other, double eps = double.MinValue * 100) + { + return false; + } + + public bool Equals(Polygon other) + { + return false; + } + + public bool Intersects(Polygon other, double eps = double.MinValue * 100) + { + return other.LinearRings.Any(l => l.Intersects(this)); + } + + public bool Touches(Polygon other, double eps = double.MinValue * 100) + { + return other.LinearRings.Any(l => l.Touches(this)); + } + + public bool Within(Polygon other, double eps = double.MinValue * 100) + { + return other.Contains(this); + } + + public bool Contains(Point other, double eps = double.NegativeInfinity) + { + return false; + } + + public bool Contains(LineSegment other, double eps = double.NegativeInfinity) + { + return false; + } + + public bool Contains(LinearRing other, double eps = double.NegativeInfinity) + { + return false; + } + + public bool Contains(Polygon other, double eps = double.NegativeInfinity) + { + return false; + } + + + #endregion Topological Operations + + #endregion Public Methods + } + + internal class LineStringEnumerator : IEnumerator where T: LineSegment + { + + #region Proprieties + public int Position { get; private set; } = 0; + + public List LineSegments { get; private set; } + + T IEnumerator.Current => Current(); + + object IEnumerator.Current => Current(); + + + #endregion Proprieties + + public LineStringEnumerator(LineString lineString) + { + LineSegments = lineString.LineSegments.Select(x => (T) x).ToList(); + } + + public T Current() + { + return this.LineSegments[Position]; + } + + public void Dispose() + { + // Suppress finalization. + GC.SuppressFinalize(this); + + } + + public bool MoveNext() + { + this.Position++; + + if (this.Position < LineSegments.Count) + { + return true; + } + else + { + return false; + } + } + + public void Reset() + { + this.Position = 0; + } + } + + } diff --git a/GeoJSON/LinearRing.cs b/GeoJSON/LinearRing.cs index b7a0939..12fbf77 100644 --- a/GeoJSON/LinearRing.cs +++ b/GeoJSON/LinearRing.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json; +using BAMCIS.GeoJSON.Serde; +using Extensions.ArrayExtensions; +using Extensions.ListExtensions; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -15,30 +18,275 @@ namespace BAMCIS.GeoJSON /// area it bounds, i.e., exterior rings are counterclockwise, and /// holes are clockwise. /// + [JsonConverter(typeof(LinearRingConverter))] public class LinearRing : LineString { + + + #region Constructors + /// /// Creates a new LinearRing /// /// The coordinates that make up the linear ring [JsonConstructor] - public LinearRing(IEnumerable coordinates, IEnumerable boundingBox = null) : base(coordinates, boundingBox) + public LinearRing(IEnumerable lineSegments) : base(lineSegments) + { + LineSegment[] lines = lineSegments.ToArray(); + + if (lines.Length < 3) + { + throw new ArgumentOutOfRangeException("A linear ring requires at least 3 lineSegments (or 4 points)."); + } + + if (!lines.First().P1.Equals(lines.Last().P2)) + { + throw new ArgumentException("The first and last Points of the LinearRing must be equivalent."); + } + } + + /// + /// Creates a new LinearRing + /// + /// The coordinates that make up the linear ring + public LinearRing(IEnumerable coordinates) : this(LinearRing.PositionsToLineSegments(coordinates)) + { + this.Points = coordinates.Select(x => x.ToPoint()).ToList() ; + + if (Points.Count() < 4) + { + throw new ArgumentOutOfRangeException("A linear ring requires at least 4 coordinates."); + } + + var c1 = this.Points.First(); + + var c2 = this.Points.Last(); + + if (!c1.Equals(c2)) + { + throw new ArgumentException("The first and last Points of the LinearRing must be equivalent."); + } + + } + + #endregion Constructors + + #region Topographic Operations + + internal new bool Touches(LineString lineString, double eps) + { + foreach(LineSegment lineSeg in lineString.LineSegments) + { + if (this.Touches(lineSeg, eps)) + { + return true; + } + } + + return false; + } + + public bool Contains(Point point) + { + if (!this.BoundingBox.Contains(point)) + { + return false; + } + + // To be considered within, no point can be an edge of the linearRing. + if (this.Points.Any(p => p.Equals(point))) + { + return false; + } + + return LinearRing_Point_Within_LinearRing_TopologicalOperations.RayCastingAlgorithm(this, point); + } + + + public bool Contains(LineSegment lineSegment) + { + return lineSegment.All(p => this.Contains(p)); + } + + public bool Contains(LineString lineString) + { + return lineString.Points.All(p => this.Contains(p)); + } + + public static bool Within(LineString _) + { + return false; + } + + public bool Within(LinearRing lineRing) + { + return this.Points.All(p => lineRing.Contains(p)); + } + + public new bool Within(Polygon polygon) + { + return polygon.Contains(this); + } + + public bool Contains(Polygon polygon) + { + return this.Contains(polygon.LinearRings.First()); + } + + #endregion Topological Operations + + + + #region Public Methods + + public int CountPointsthatComposeThisLineRing() { - Position[] coords = this.Coordinates.ToArray(); + int points = 0; - if (coords.Length < 4) + foreach(LineSegment line in LineSegments) { + + // Each linesegment is composed of solely 2 points: + + points += 2; + } + + return points; + } + + public static LineString CoordinatesToLineString(IEnumerable coordinates) + { + List lineSegments = PositionsToLineSegments(coordinates); + + return new LineString(lineSegments); + } + + public static List PositionsToLineSegments(IEnumerable coordinates) + { + var positions = coordinates.ToList(); + var lineSegments = new List { }; + + var position_init = positions.Pop(); + + Coordinate prior_position = position_init.Copy(); + + Coordinate after_position; + + var NPositions = positions.Count; + + for (var i = 0; i < NPositions; i++) { - throw new ArgumentOutOfRangeException("A linear ring requires 4 or more positions."); + + after_position = positions.Pop(); + + var lineSegment = new LineSegment(new Point(prior_position), + new Point(after_position) + ); + + lineSegments.Add(lineSegment); + + prior_position = after_position; + } - if (!coords.First().Equals(coords.Last())) + var closingLineSegment = new LineSegment(new Point(prior_position), + new Point(position_init) + ); + + lineSegments.Add(closingLineSegment); + + return lineSegments; + } + + #endregion Public Methods + + + } + + internal class LinearRing_Point_Within_LinearRing_TopologicalOperations + { + + public static bool CrossVectorProductAlgorithm(LinearRing lineRing, Point point) + { + var points = lineRing.Points.ToList(); + + int signIsAlwaysEquals = 0; + + for (int i = 1; i < points.Count; i++) { - throw new ArgumentException("The first and last value must be equivalent.", "coordinates"); + var Vii = points[i]; + + var Vi = points[i - 1]; + + var Vii_Vi = Vii - Vi; + + var PVI = ( point - Vi ); + + + var CrossProd = Vii_Vi.ToArray().CrossProduct2D(PVI.ToArray()); + + if (CrossProd > 0) + { + if (signIsAlwaysEquals < 0) + { + return false; + } + else + { + signIsAlwaysEquals = -1; + } + } + else if (CrossProd == 0) + { + continue; + } + else + { + if (signIsAlwaysEquals > 0) + { + return false; + } + else + { + signIsAlwaysEquals = -1; + } + } } + + return true; } - #endregion + public static bool RayCastingAlgorithm(LinearRing lineRing, Point point) + { + int lineSegmentsThatAreIntersectedByPoint = 0; + var ypoint = point.GetLatitude(); + var xpoint = point.GetLongitude(); + + foreach (LineSegment lineSeg in lineRing.LineSegments) + { + var p1 = lineSeg.P1; + var x1 = p1.GetLongitude(); + var y1 = p1.GetLatitude(); + + var p2 = lineSeg.P2; + var x2 = p2.GetLongitude(); + var y2 = p2.GetLatitude(); + + var xLimit = x1 + ( ( ypoint - y1 ) * ( x2 - x1 ) / ( y2 - y1 ) ); + + // for the casting ray (to the right from the provided point) to be capable of + // intersecting the line segment, the xpoint must be to the left of the linesegment. + if (xpoint < xLimit && + ypoint > lineSeg.BoundingBox.MinLatitude && + ypoint < lineSeg.BoundingBox.MaxLatitude + ) + { + lineSegmentsThatAreIntersectedByPoint++; + } + } + + return lineSegmentsThatAreIntersectedByPoint % 2 == 1; + } } } diff --git a/GeoJSON/MultiLineString.cs b/GeoJSON/MultiLineString.cs index c56d943..bc77e64 100644 --- a/GeoJSON/MultiLineString.cs +++ b/GeoJSON/MultiLineString.cs @@ -1,6 +1,7 @@ using BAMCIS.GeoJSON.Serde; using Newtonsoft.Json; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -11,7 +12,7 @@ namespace BAMCIS.GeoJSON /// LineString coordinate arrays. /// [JsonConverter(typeof(MultiLineStringConverter))] - public class MultiLineString : Geometry + public class MultiLineString : Geometry, IEnumerable { #region Public Properties @@ -19,10 +20,25 @@ public class MultiLineString : Geometry /// For type "MultiLineString", the "coordinates" member is an array of /// LineString coordinate arrays. /// - [JsonProperty(PropertyName = "coordinates")] - public IEnumerable Coordinates { get; } + [JsonProperty(PropertyName = "Points")] + public IEnumerable LineStrings { get; } - #endregion + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox + { + get + { + + if (this._BoundingBox == null) + { + this._BoundingBox = FetchBoundingBox(); + } + return this._BoundingBox; + } + } + + #endregion Public Properties #region Constructors @@ -31,20 +47,53 @@ public class MultiLineString : Geometry /// /// The line strings that make up the object [JsonConstructor] - public MultiLineString(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.MultiLineString, coordinates.Any(x => x.IsThreeDimensional()), boundingBox) + public MultiLineString(IEnumerable coordinates) : base(GeoJsonType.MultiLineString, coordinates.Any(x => x.IsThreeDimensional())) { - this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); + this.LineStrings = coordinates ?? throw new ArgumentNullException("Points"); } #endregion #region Public Methods + #region Converters + public static new MultiLineString FromJson(string json) { return JsonConvert.DeserializeObject(json); } + #endregion Converters + + #region Enumerable + + + public IEnumerable ToList() + { + foreach (var geometry in this.LineStrings) + { + + yield return geometry; + } + } + + public IEnumerator GetEnumerator() + { + foreach (var line in LineStrings) + { + yield return line; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + + #endregion Enumerable + + #region Equality Evaluators public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) @@ -72,13 +121,13 @@ public override bool Equals(object obj) bool coordinatesEqual = true; - if (this.Coordinates != null && other.Coordinates != null) + if (this.LineStrings != null && other.LineStrings != null) { - coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); + coordinatesEqual = this.LineStrings.SequenceEqual(other.LineStrings); } else { - coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); + coordinatesEqual = (this.LineStrings == null && other.LineStrings == null); } return this.Type == other.Type && @@ -88,9 +137,10 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); + return Hashing.Hash(this.Type, this.LineStrings, this.BoundingBox); } + public static bool operator ==(MultiLineString left, MultiLineString right) { if (ReferenceEquals(left, right)) @@ -111,6 +161,53 @@ public override int GetHashCode() return !(left == right); } + #endregion Equality Evaluators + + #region Topological Operations + public Rectangle FetchBoundingBox() + { + + double MaxLatitude = double.MinValue; + double MaxLongitude = double.MinValue; + double MinLatitude = double.MaxValue; + double MinLongitude = double.MaxValue; + + foreach (var geometry in this.LineStrings) + { + if (MaxLatitude < geometry.BoundingBox.MaxLatitude) + { + MaxLatitude = geometry.BoundingBox.MaxLatitude; + } + + if (MaxLongitude < geometry.BoundingBox.MaxLongitude) + { + MaxLongitude = geometry.BoundingBox.MaxLongitude; + } + + if (MinLatitude > geometry.BoundingBox.MinLatitude) + { + MinLatitude = geometry.BoundingBox.MinLatitude; + } + + if (MinLongitude > geometry.BoundingBox.MinLongitude) + { + MinLongitude = geometry.BoundingBox.MinLongitude; + } + } + + Point LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + Point LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + Point UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + Point UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); + + } + + + + #endregion Topological Operations + #endregion } } diff --git a/GeoJSON/MultiPoint.cs b/GeoJSON/MultiPoint.cs index 0f6d809..3d0ba96 100644 --- a/GeoJSON/MultiPoint.cs +++ b/GeoJSON/MultiPoint.cs @@ -2,6 +2,8 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace BAMCIS.GeoJSON @@ -9,7 +11,7 @@ namespace BAMCIS.GeoJSON /// /// For type "MultiPoint", the "coordinates" member is an array of positions. /// - [JsonConverter(typeof(InheritanceBlockerConverter))] + [JsonConverter(typeof(MultiPointConverter))] public class MultiPoint : Geometry { #region Public Properties @@ -17,10 +19,27 @@ public class MultiPoint : Geometry /// /// The coordinates of a multipoint are an array of positions /// - [JsonProperty(PropertyName = "coordinates")] - public IEnumerable Coordinates { get; } + [JsonProperty(PropertyName = "Points")] + [Description("Points")] + public IEnumerable Points { get; } - #endregion + + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox + { + get + { + + if (this._BoundingBox == null) + { + this._BoundingBox = FetchBoundingBox(); + } + return this._BoundingBox; + } + } + + #endregion Public Properties #region Constructors @@ -28,15 +47,48 @@ public class MultiPoint : Geometry /// Creates a new multipoint object /// /// - public MultiPoint(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.MultiPoint, coordinates.Any(x => x.HasElevation()), boundingBox) + [JsonConstructor] + public MultiPoint([NotNull] IEnumerable coordinates) : base(GeoJsonType.MultiPoint, coordinates.Any(x => x.HasElevation())) + { + if (coordinates == null) + throw new ArgumentNullException(nameof(Coordinate)); + + this.Points = coordinates.Select(c => new Point(c)); + } + + /// + /// Creates a new multipoint object + /// + /// + + public MultiPoint([NotNull] IEnumerable> coordinates) : base(GeoJsonType.MultiPoint, coordinates.Any(x => x.Any(y => y.HasElevation()))) + { + var points = new List(); + + foreach(IEnumerable pointCoordinates in coordinates) + { + points.AddRange(pointCoordinates.Select(p => p.ToPoint()).ToList()); + } + + this.Points = points; + } + + + + public MultiPoint([NotNull] IEnumerable points) : base(GeoJsonType.MultiPoint, points.Any(x => x.HasElevation())) { - this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); + if (points == null) + throw new ArgumentNullException(nameof(Coordinate)); + + this.Points = points; } - #endregion + #endregion Constructors #region Public Methods + #region Comparers + public static new MultiPoint FromJson(string json) { return JsonConvert.DeserializeObject(json); @@ -69,13 +121,13 @@ public override bool Equals(object obj) bool coordinatesEqual = true; - if (this.Coordinates != null && other.Coordinates != null) + if (this.Points != null && other.Points != null) { - coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); + coordinatesEqual = this.Points.SequenceEqual(other.Points); } else { - coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); + coordinatesEqual = (this.Points == null && other.Points == null); } return this.Type == other.Type && @@ -85,7 +137,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); + return Hashing.Hash(this.Type, this.Points, this.BoundingBox); } public static bool operator ==(MultiPoint left, MultiPoint right) @@ -108,6 +160,54 @@ public override int GetHashCode() return !(left == right); } - #endregion + #endregion Comparers + + + #region Topological Operations + public Rectangle FetchBoundingBox() + { + + double MaxLatitude = double.MinValue; + double MaxLongitude = double.MinValue; + double MinLatitude = double.MaxValue; + double MinLongitude = double.MaxValue; + + foreach (Point geometry in this.Points) + { + if (MaxLatitude < geometry.GetLatitude()) + { + MaxLatitude = geometry.GetLatitude(); + } + + if (MaxLongitude < geometry.GetLongitude()) + { + MaxLongitude = geometry.GetLongitude(); + } + + if (MinLatitude > geometry.GetLatitude()) + { + MinLatitude = geometry.GetLatitude(); + } + + if (MinLongitude > geometry.GetLongitude()) + { + MinLongitude = geometry.GetLongitude(); + } + } + + var LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + var LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + var UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + var UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); + + } + + + #endregion Topological Operations + + + #endregion Public Methods } } diff --git a/GeoJSON/MultiPolygon.cs b/GeoJSON/MultiPolygon.cs index f1114b8..6320828 100644 --- a/GeoJSON/MultiPolygon.cs +++ b/GeoJSON/MultiPolygon.cs @@ -18,8 +18,23 @@ public class MultiPolygon : Geometry /// /// The coordinates are an array of polygons. /// - [JsonProperty(PropertyName = "coordinates")] - public IEnumerable Coordinates { get; } + [JsonProperty(PropertyName = "Polygons")] + public IEnumerable Polygons { get; } + + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox + { + get + { + + if (this._BoundingBox == null) + { + this._BoundingBox = FetchBoundingBox(); + } + return this._BoundingBox; + } + } #endregion @@ -28,21 +43,68 @@ public class MultiPolygon : Geometry /// /// Creates a new MultiPolygon /// - /// The coordinates that make up the multi polygon + /// The coordinates that make up the multi polygon [JsonConstructor] - public MultiPolygon(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.MultiPolygon, coordinates.Any(x => x.IsThreeDimensional()), boundingBox) + public MultiPolygon(IEnumerable Polygons) : base(GeoJsonType.MultiPolygon, Polygons.Any(x => x.IsThreeDimensional())) { - this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); + this.Polygons = Polygons ?? throw new ArgumentNullException(nameof(Polygons)); - if (!this.Coordinates.Any()) + if (!this.Polygons.Any()) { - throw new ArgumentOutOfRangeException("coordinates", "A MultiPolygon must have at least 1 polygon."); + throw new ArgumentOutOfRangeException(nameof(Polygons), "A MultiPolygon must have at least 1 polygon."); } + } #endregion #region Public Methods + + public Rectangle FetchBoundingBox() + { + + double MaxLatitude = double.MinValue ; + double MaxLongitude = double.MinValue; + double MinLatitude = double.MaxValue; + double MinLongitude = double.MaxValue; + + foreach (var polygon in this.Polygons) + { + if (MaxLatitude < polygon.BoundingBox.MaxLatitude) + { + MaxLatitude = polygon.BoundingBox.MaxLatitude; + } + + if (MaxLongitude < polygon.BoundingBox.MaxLongitude) + { + MaxLongitude = polygon.BoundingBox.MaxLongitude; + } + + if (MinLatitude > polygon.BoundingBox.MinLatitude) + { + MinLatitude = polygon.BoundingBox.MinLatitude; + } + + if (MinLongitude > polygon.BoundingBox.MinLongitude) + { + MinLongitude = polygon.BoundingBox.MinLongitude; + } + } + + Point LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + Point LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + Point UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + Point UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); + + } + + /// + /// Deserializes the json into a MultiPolygon + /// + /// The json to deserialize + /// A Point object public static new MultiPolygon FromJson(string json) { return JsonConvert.DeserializeObject(json); @@ -76,13 +138,13 @@ public override bool Equals(object obj) bool coordinatesEqual = true; - if (this.Coordinates != null && other.Coordinates != null) + if (this.Polygons != null && other.Polygons != null) { - coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); + coordinatesEqual = this.Polygons.SequenceEqual(other.Polygons); } else { - coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); + coordinatesEqual = (this.Polygons == null && other.Polygons == null); } return this.Type == other.Type && @@ -92,9 +154,10 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); + return Hashing.Hash(this.Type, this.Polygons, this.BoundingBox); } + public static bool operator ==(MultiPolygon left, MultiPolygon right) { if (ReferenceEquals(left, right)) diff --git a/GeoJSON/Point.cs b/GeoJSON/Point.cs index 4ce724e..3e311cc 100644 --- a/GeoJSON/Point.cs +++ b/GeoJSON/Point.cs @@ -1,25 +1,28 @@ using BAMCIS.GeoJSON.Serde; using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Linq; namespace BAMCIS.GeoJSON { /// /// For type "Point", the "coordinates" member is a single position. /// - [JsonConverter(typeof(InheritanceBlockerConverter))] - public class Point : Geometry + [JsonConverter(typeof(PointConverter))] + public class Point : Geometry, + IAdimTopology, + IAdimTopology { #region Public Properties /// /// For type "Point", the "coordinates" member is a single position. /// - [JsonProperty(PropertyName = "coordinates")] - public Position Coordinates { get; } + [JsonProperty(PropertyName = "Coordinates")] + public Coordinate Coordinates { get; } + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox { get { return null; } } #endregion #region Constructors @@ -29,15 +32,30 @@ public class Point : Geometry /// /// The position of this point [JsonConstructor] - public Point(Position coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.Point, coordinates.HasElevation(), boundingBox) + public Point(Coordinate coordinates) : base(GeoJsonType.Point, coordinates.HasElevation()) { - this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); + this.Coordinates = coordinates ?? throw new ArgumentNullException(nameof(coordinates)); + + } + + public Point(double longitude, double latitude) : base(GeoJsonType.Point, false) + { + this.Coordinates = new Coordinate(longitude, latitude) ?? throw new ArgumentNullException("Longitude and Latitude are invalid"); + + } + + public Point(double longitude, double latitude, double altitude) : base(GeoJsonType.Point, true) + { + this.Coordinates = new Coordinate(longitude, latitude, altitude) ?? throw new ArgumentNullException("Longitude and Latitude and Altitude are invalid"); + } #endregion #region Public Methods + #region Converters + /// /// Deserializes the json into a Point /// @@ -48,6 +66,22 @@ public Point(Position coordinates, IEnumerable boundingBox = null) : bas return JsonConvert.DeserializeObject(json); } + /// + /// Converts this Point into a 1D Array of length 2 (longitude and latitude) + /// + /// + public double[] ToArray() + { + var vector = new double[] { this.Coordinates.Longitude, this.Coordinates.Latitude }; + + return vector; + + } + + #endregion Converters + + #region Coordinates + /// /// Gets the longitude or easting of the point /// @@ -66,11 +100,6 @@ public double GetLatitude() return this.Coordinates.Latitude; } - /// - /// Gets the elevation of the point if it exists - /// in the coordinates. - /// - /// The elevation or if it wasn't set, the returns NaN public bool TryGetElevation(out double elevation) { if (this.Coordinates.HasElevation()) @@ -83,41 +112,45 @@ public bool TryGetElevation(out double elevation) return false; } - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj == null || this.GetType() != obj.GetType()) - { - return false; - } + #endregion Coordinates - Point other = (Point)obj; + /// + /// Gets the elevation of the point if it exists + /// in the coordinates. + /// + /// The elevation or if it wasn't set, the returns NaN - bool bBoxEqual = true; + #region Equality Evaluators - if (this.BoundingBox != null && other.BoundingBox != null) - { - bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); - } - else + public bool Equals(Point obj) + { + return obj.Coordinates.Equals(this.Coordinates); + } + + public bool Equals(Polygon other) + { + return false; + } + + public override bool Equals(object obj) + { + var point = obj as Point; + + if (point == null ) { - bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); + return false; } - return this.Type == other.Type && - this.Coordinates == other.Coordinates && - bBoxEqual; + return Equals(point); } public override int GetHashCode() { - return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); + return Hashing.Hash(this.Type, this.Coordinates); } + public static bool operator ==(Point left, Point right) { if (ReferenceEquals(left, right)) @@ -133,11 +166,137 @@ public override int GetHashCode() return left.Equals(right); } + public static Point operator -(Point left, Point right) + { + + Coordinate newPosition = left.Coordinates - right.Coordinates; + + return new Point(newPosition); + } + + public static Point operator +(Point left, Point right) + { + Coordinate newPosition = left.Coordinates + right.Coordinates; + + return new Point(newPosition); + } + public static bool operator !=(Point left, Point right) { return !(left == right); } - #endregion + + #endregion Equality Evaluators + + #region Topographic Operations + + #region Touching Rules + + public bool Touches(Point otherPoint, double eps = double.MinValue * 100) + { + return this.Equals(otherPoint); + } + public bool Touches(LineSegment lineSegment) + { + return lineSegment.Touches(this); + } + + + public bool Touches(LineString lineString, double eps = double.MinValue * 100) + { + foreach (LineSegment lineSegment in lineString) + { + if (lineSegment.Touches(this, eps)) + { + return true; + } + else + { + continue; + } + } + return false; + } + + public bool Touches(Polygon polygon, double eps = double.MinValue * 100) + { + foreach (var lineRing in polygon) + { + if (this.Touches(lineRing, eps)) + { + return true; + } + else + { + continue; + } + } + + return false; + } + #endregion Touching Rules + + #region Within Rules + + + + public bool Within(Point point, double eps = double.MinValue * 100) + { + return this.Equals(point); + } + + + public bool Within(Polygon polygon, double eps = double.MinValue * 100) + { + return polygon.Contains(this, eps); + } + + /// + /// A point always has an empty Rectangle as its bounding box. + /// + /// + public Rectangle FetchBoundingBox() + { + return null; + } + + public Point Copy() + { + return new Point(this.Coordinates.Copy()); + } + + public bool HasElevation() + { + return this.Coordinates.HasElevation(); + } + + + + #endregion Within Rules + + #region Intersection Rules + + public static bool Intersects(Geometry _) + { + return false; + } + + #endregion Intersection Rules + + + #region Contain Rules + + public static bool Contains(Geometry _) + { + return false; + } + + #endregion Contain Rules + + + #endregion Topographic Operations + + #endregion Public Methods } -} +} \ No newline at end of file diff --git a/GeoJSON/Polygon.cs b/GeoJSON/Polygon.cs index 341ed0d..1c0eae5 100644 --- a/GeoJSON/Polygon.cs +++ b/GeoJSON/Polygon.cs @@ -1,22 +1,33 @@ using BAMCIS.GeoJSON.Serde; +using Extensions.ArrayExtensions; using Newtonsoft.Json; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; namespace BAMCIS.GeoJSON { + + /// - /// A polygon is formed with 1 or more linear rings, which are an enclosed LineString + /// A polygon is formed with 1 or more linear rings, which are an enclosed LineString. + /// + /// + /// For spatial operations, check: + /// https://www.topcoder.com/thrive/articles/Geometry%20Concepts%20part%203:%20Using%20Geometry%20in%20Topcoder%20Problems /// [JsonConverter(typeof(PolygonConverter))] - public class Polygon : Geometry - { - #region Private Fields + public class Polygon : Geometry, + IEquatable, + IContains, + ITouch, + IPolygon, + IPolygon, + IEnumerable - private IEnumerable _coordinates; - - #endregion + { + #region Public Properties @@ -27,11 +38,36 @@ public class Polygon : Geometry /// exterior ring bounds the surface, and the interior rings(if /// present) bound holes within the surface. /// - [JsonProperty(PropertyName = "coordinates")] - public IEnumerable Coordinates { + [JsonProperty(PropertyName = "LinearRings")] + [JsonIgnore] + public IEnumerable LinearRings { get; set; } + + + /// + /// The points that represent the linearRings of the polygon + /// + [JsonProperty(PropertyName = "Points")] + [JsonIgnore] + public IEnumerable Points + { + get + { + return this.LinearRings.SelectMany(lineRing => lineRing.Points).ToList(); + } + } + + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox + { get { - return this._coordinates; + + if (this._BoundingBox == null) + { + this._BoundingBox = FetchBoundingBox(); + } + return this._BoundingBox; } } @@ -44,39 +80,74 @@ public IEnumerable Coordinates { /// /// The linear rings that make up the polygon [JsonConstructor] - public Polygon(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.Polygon, coordinates.Any(x => x.IsThreeDimensional()), boundingBox) + public Polygon(IEnumerable linearRings) : base(GeoJsonType.Polygon, linearRings.Any(x => x.IsThreeDimensional())) { - this._coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); + this.LinearRings = linearRings ?? throw new ArgumentNullException("Points"); - if (!this.Coordinates.Any()) + if (!this.LinearRings.Any()) { - throw new ArgumentOutOfRangeException("coordinates", "A polygon must have at least 1 linear ring."); + throw new ArgumentOutOfRangeException("Points", "A polygon must have at least 1 linear ring."); } } - #endregion - - #region Public Methods + /// - /// Removes the interior linear rings that bound holes within the surface from the polygon's coordinates - /// leaving just 1 linear ring in the coordinates. + /// Creates a new Polygon /// - /// Returns true if the polygon had more than linear ring and false if there were no linear rings to remove - public bool RemoveInteriorRings() + /// The linear rings that make up the polygon + + public Polygon(LinearRing linearRing) : base(GeoJsonType.Polygon, linearRing.Any(x => x.HasElevation())) { - // If there is more than element - if (this._coordinates != null && this._coordinates.Skip(1).Any()) + this.LinearRings = new List { linearRing } ?? throw new ArgumentNullException("Points"); + + if (!this.LinearRings.Any()) { - this._coordinates = this._coordinates.Take(1); - return true; + throw new ArgumentOutOfRangeException("Points", "A polygon must have at least 1 linear ring."); } - else + } + + + public Polygon(IEnumerable>> coordinates) : this(CoordinatesToLinearRings(coordinates)) + { + + } + + public static IEnumerable CoordinatesToLinearRings(IEnumerable>> coordinates) + { + var rings = new List(); + + foreach (var lineRing in coordinates) { - return false; + var positionsOfASingleLineRing = new List(); + + foreach (var lineSegments in lineRing) + { + List posWithinALineSegment = lineSegments.ToList(); + positionsOfASingleLineRing.AddRange(posWithinALineSegment); + } + + var ring = new LinearRing(LineSegment.PositionsToLineSegments(positionsOfASingleLineRing)); + + rings.Add(ring); } + + return rings; } + + #endregion + + #region Public Methods + + /// + /// Removes the interior linear rings that bound holes within the surface from the polygon's coordinates + /// leaving just 1 linear ring in the coordinates. + /// + /// Returns true if the polygon had more than linear ring and false if there were no linear rings to remove + + #region Comparable Methods + /// /// Deserializes the json into a Polygon /// @@ -99,40 +170,59 @@ public override bool Equals(object obj) return false; } - Polygon other = (Polygon)obj; + var other = obj as Polygon; - bool bBoxEqual = true; + if (other == null) + { + return false; + } + + return this.Equals(other); + + } + + + public bool Equals(Polygon other) + { + bool bBoxEqual; if (this.BoundingBox != null && other.BoundingBox != null) { - bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); + bBoxEqual = this.BoundingBox.Equals(other.BoundingBox); } else { - bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); + bBoxEqual = ( this.BoundingBox == null && other.BoundingBox == null ); } - bool coordinatesEqual = true; - - if (this.Coordinates != null && other.Coordinates != null) + if (bBoxEqual) { - coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); + bool coordinatesEqual = true; + + if (this.LinearRings != null && other.LinearRings != null) + { + coordinatesEqual = this.LinearRings.SequenceEqual(other.LinearRings); + } + else + { + coordinatesEqual = ( this.LinearRings == null && other.LinearRings == null ); + } + + return coordinatesEqual && this.Type == other.Type; } + else { - coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); + return false; } - - return this.Type == other.Type && - coordinatesEqual && - bBoxEqual; } public override int GetHashCode() { - return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); + return Hashing.Hash(this.Type, this.LinearRings, this.BoundingBox); } + public static bool operator ==(Polygon left, Polygon right) { if (ReferenceEquals(left, right)) @@ -153,6 +243,252 @@ public override int GetHashCode() return !(left == right); } - #endregion + #endregion Comparable Methods + + + #region Enumeration + + public IEnumerator GetEnumerator() + { + foreach (var line in LinearRings) + { + yield return line; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion Enumeration + + + #region Topological Operations + + public Rectangle FetchBoundingBox() + { + double MinLongitude = double.MaxValue; + + double MaxLongitude = double.MinValue; + + double MinLatitude = double.MaxValue; + + double MaxLatitude = double.MinValue; + + + foreach (LinearRing line in this.LinearRings) + { + if (MinLongitude > line.BoundingBox.MinLongitude) + { + MinLongitude = line.BoundingBox.MinLongitude; + } + + if (MaxLongitude < line.BoundingBox.MaxLongitude) + { + MaxLongitude = line.BoundingBox.MaxLongitude; + } + + if (MinLatitude > line.BoundingBox.MinLatitude) + { + MinLatitude = line.BoundingBox.MinLatitude; + } + + if (MaxLatitude < line.BoundingBox.MaxLatitude) + { + MaxLatitude = line.BoundingBox.MaxLatitude; + } + } + + var LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + + var LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + + var UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + + var UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + return new Rectangle(LL, LR, UL, UR); + + } + + public void RemoveInteriorRings() + { + // If there is more than element + + + if (this.LinearRings != null && this.LinearRings.Count()>1) + { + this.LinearRings = new List { this.LinearRings.First() }; + } + } + + public LinearRing OuterRing() + { + return this.LinearRings.First(); + } + + public bool Contains(Point point, double eps = double.MinValue * 100) + { + + if (!this.BoundingBox.Contains(point)) + { + return false; + } + + int numberOfRingsThatContainPoint = 0; + bool firstRingContainsPoint = false; + + int ringCounter = 0; + foreach (var ring in LinearRings) + { + if(ring.Contains(point)) + { + if (ringCounter == 0) + { + firstRingContainsPoint = true; + } + else + { + // No inner ring can contain point. Only the first ring (i.e., the outer ring) can contain a point + // for a polygon to be considered as containing that point. + return false; + } + + numberOfRingsThatContainPoint++; + } + + ringCounter++; + } + + if (firstRingContainsPoint) + { + return true; + } + else + { + return false; + } + } + + + public bool Intersects(Point point) + { + return false; + } + + public bool Touches(Point point, double eps = double.MinValue * 100) + { + + foreach(LinearRing lineRing in LinearRings) + { + if (lineRing.Touches(point, eps)) + { + return true; + } + else + { + continue; + } + } + return false; + } + + public bool Touches(LineString lineString, double eps = double.MinValue * 100) + { + foreach (LinearRing lineRing in LinearRings) + { + if (lineRing.Touches(lineString, eps)) + { + return true; + } + else + { } + } + return false; + } + + + public bool Contains(LineString lineString, double eps = double.MinValue * 100) + { + return lineString.Points.All(l => this.Contains(l)); + } + + /// + /// In order to check if a lineSegment is within a polygon, one must do three steps: + /// 1) sort the edges of the polygon in clock-wise (or counter clock-wise) order + /// 2) evaluate the cross-product of (V[i+1] - V[i]) x (P - V[i]) for every i-esimal vertice within the polygon + /// 3) compare the sign of all these cross-products. if they are all of same sign (e.g., all positive, or all negative), + /// then point P is within the polygon. + /// + /// + /// + /// + /// + public bool Contains(LineSegment lineSegment, double eps = double.MinValue * 100) + { + return lineSegment.Points.All(p => this.Contains(p, eps)); + } + + public bool Intersects(LineSegment lineSegment, double eps = double.MinValue * 100) + { + int pointWithin = 0; + foreach (Point point in lineSegment.Points) + { + if (point.Touches(this, eps)) + { + pointWithin ++; + } + + } + + if (pointWithin > 1 || pointWithin < 0) + { + return false; + } + else + { + return true; + } + } + + public bool Intersects(LineString lineString, double eps = double.MinValue * 100) + { + + foreach (LineSegment lineSeg in lineString.LineSegments) + { + if (this.Intersects(lineSeg, eps)) + { + return true; + } + + } + + return true; + + } + + /// + /// Return The Points that compose the outer-ring of the polygon. + /// + /// + public IEnumerable FetchEdge() + { + return LinearRings.First().Points; + } + + internal bool Within(LinearRing linearRing) + { + return linearRing.Contains(this); + } + internal bool Within(Polygon polygon) + { + return polygon.Contains(this.LinearRings.First()); + } + + #endregion Topological Operations + + + #endregion Public Methods } } diff --git a/GeoJSON/Position.cs b/GeoJSON/Position.cs index 0c9721f..35cab1e 100644 --- a/GeoJSON/Position.cs +++ b/GeoJSON/Position.cs @@ -9,7 +9,7 @@ namespace BAMCIS.GeoJSON /// A GeoJSON position consisting of a longitude, latitude, and optional elevation /// [JsonConverter(typeof(PositionConverter))] - public class Position : IEquatable, IEqualityComparer + public class Coordinate : IEquatable, IEqualityComparer { #region Public Properties @@ -43,7 +43,7 @@ public class Position : IEquatable, IEqualityComparer /// /// The position's longitude /// The position's latitude - public Position(double longitude, double latitude) : this(longitude, latitude, double.NaN) + public Coordinate(double longitude, double latitude) : this(longitude, latitude, double.NaN) { } @@ -54,23 +54,23 @@ public Position(double longitude, double latitude) : this(longitude, latitude, d /// The position's latitude /// The position's elevation [JsonConstructor] - public Position(double longitude, double latitude, double elevation) + public Coordinate(double longitude, double latitude, double elevation) { if (double.IsInfinity(latitude) || double.IsNaN(latitude)) { - throw new ArgumentOutOfRangeException("latitude", "The latitude cannot be NaN or infinity."); + throw new ArgumentOutOfRangeException(nameof(latitude), "The latitude cannot be NaN or infinity."); } if (double.IsInfinity(longitude) || double.IsNaN(longitude)) { - throw new ArgumentOutOfRangeException("longitude", "The longitude cannot be NaN or infinity."); + throw new ArgumentOutOfRangeException(nameof(longitude), "The longitude cannot be NaN or infinity."); } if (!GeoJsonConfig.IgnoreLongitudeValidation) { if (longitude < -180 || longitude > 180) { - throw new ArgumentOutOfRangeException("longitude", "Longitude must be between -180 and 180 degrees, inclusive."); + throw new ArgumentOutOfRangeException(nameof(longitude), "Longitude must be between -180 and 180 degrees, inclusive."); } } @@ -78,13 +78,13 @@ public Position(double longitude, double latitude, double elevation) { if (latitude < -90 || latitude > 90) { - throw new ArgumentOutOfRangeException("latitude", "Latitude must be between -90 and 90 degrees, inclusive."); + throw new ArgumentOutOfRangeException(nameof(latitude), "Latitude must be between -90 and 90 degrees, inclusive."); } } if (double.IsInfinity(elevation)) { - throw new ArgumentOutOfRangeException("elevation", "The elevation cannot be infinity."); + throw new ArgumentOutOfRangeException(nameof(elevation), "The elevation cannot be infinity."); } this.Latitude = latitude; @@ -96,14 +96,21 @@ public Position(double longitude, double latitude, double elevation) #region Public Methods + public double[] ToArray() + { + double[] array = new double[]{ this.Longitude, this.Latitude}; + + return array; + } + /// /// Deserializes the provided json into a Position object /// /// The json to deserialize /// A Position object - public static Position FromJson(string json) + public static Coordinate FromJson(string json) { - return JsonConvert.DeserializeObject(json); + return JsonConvert.DeserializeObject(json); } /// @@ -127,7 +134,7 @@ public override bool Equals(object obj) return false; } - Position other = (Position)obj; + Coordinate other = (Coordinate)obj; bool temp = this.Latitude == other.Latitude && this.Longitude == other.Longitude; @@ -140,7 +147,7 @@ public override bool Equals(object obj) return temp; } - public bool Equals(Position other) + public bool Equals(Coordinate other) { if (ReferenceEquals(this, other)) { @@ -148,19 +155,28 @@ public bool Equals(Position other) } else { - bool temp = this.Latitude == other.Latitude && - this.Longitude == other.Longitude; + bool temp = (this.Latitude == other.Latitude && + this.Longitude == other.Longitude); - if (!double.IsNaN(this.Elevation) || !double.IsNaN(other.Elevation)) + if (double.IsNaN(this.Elevation) && double.IsNaN(other.Elevation)) + { + return temp; + } + else if (!double.IsNaN(this.Elevation) && !double.IsNaN(other.Elevation)) { - temp = temp && (this.Elevation == other.Elevation); + temp = temp && ( this.Elevation == other.Elevation ); + + return temp; } - return temp; + else // (!double.IsNaN(this.Elevation) || !double.IsNaN(other.Elevation)) + { + return false; + } } } - public bool Equals(Position left, Position right) + public bool Equals(Coordinate left, Coordinate right) { return left == right; } @@ -175,7 +191,7 @@ public override string ToString() return $"[{this.Longitude},{this.Latitude}{(!double.IsNaN(this.Elevation) ? $",{this.Elevation}" : "")}]"; } - public static bool operator ==(Position left, Position right) + public static bool operator ==(Coordinate left, Coordinate right) { if (ReferenceEquals(left, right)) { @@ -190,16 +206,83 @@ public override string ToString() return left.Equals(right); } - public static bool operator !=(Position left, Position right) + public static bool operator !=(Coordinate left, Coordinate right) { return !(left == right); } - public int GetHashCode(Position other) + + public static Coordinate operator -(Coordinate left, Coordinate right) + { + var newPosition = new Coordinate(left.Longitude - right.Longitude, left.Latitude - right.Latitude); + + return newPosition; + } + + public static Coordinate operator -(Coordinate left, double translation) + { + var newPosition = new Coordinate(left.Longitude - translation, left.Latitude - translation); + + return newPosition; + } + + public static Coordinate operator +(Coordinate left, Coordinate right) + { + var newPosition = new Coordinate(left.Longitude + right.Longitude, left.Latitude + right.Latitude); + + return newPosition; + } + + public static Coordinate operator +(Coordinate left, double translation) + { + var newPosition = new Coordinate(left.Longitude + translation, left.Latitude + translation); + + return newPosition; + } + + public static Coordinate operator *(Coordinate left, Coordinate right) + { + var newPosition = new Coordinate(left.Longitude * right.Longitude, left.Latitude * right.Latitude); + + return newPosition; + } + + public static Coordinate operator *(Coordinate left, double translation) + { + var newPosition = new Coordinate(left.Longitude * translation, left.Latitude * translation); + + return newPosition; + } + + public static Coordinate operator /(Coordinate left, Coordinate right) + { + var newPosition = new Coordinate(left.Longitude / right.Longitude, left.Latitude / right.Latitude); + + return newPosition; + } + + public static Coordinate operator /(Coordinate left, double translation) + { + var newPosition = new Coordinate(left.Longitude / translation, left.Latitude / translation); + + return newPosition; + } + + public int GetHashCode(Coordinate other) { return other.GetHashCode(); } + internal Coordinate Copy() + { + return new Coordinate(this.Longitude, this.Latitude); + } + + internal Point ToPoint() + { + return new Point(this.Longitude, this.Latitude); + } + #endregion } } diff --git a/GeoJSON/Rectangle.cs b/GeoJSON/Rectangle.cs new file mode 100644 index 0000000..de148cc --- /dev/null +++ b/GeoJSON/Rectangle.cs @@ -0,0 +1,286 @@ +using BAMCIS.GeoJSON.Serde; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace BAMCIS.GeoJSON +{ + + [JsonConverter(typeof(InheritanceBlockerConverter))] + public class Rectangle : Polygon, + IEquatable + { + #region Properties + public double MinLongitude { get; set; } = double.MaxValue; + + public double MaxLongitude { get; set; } = double.MinValue; + + + public double MinLatitude { get; set; } = double.MaxValue; + + + public double MaxLatitude { get; set; } = double.MinValue; + + /// + /// Lower Left corner of the Bounding Box + /// + public Point LL { get; set; } + + /// + /// Lower Right corner of the Bounding Box + /// + public Point LR { get; set; } + + /// + /// Upper Left corner of the Bounding Box + /// + public Point UL { get; set; } + + /// + /// Upper Right corner of the Bounding Box + /// + public Point UR { get; set; } + + + /// + /// Returns the LL, LR, UL, UR points of this bounding box + /// + [JsonProperty(PropertyName = "Points")] + [JsonIgnore] + public new IEnumerable Points + { + get + { + return new List { LL, LR, UL, UR }; + } + } + + + [JsonProperty(PropertyName = "BoundingBox")] + [JsonIgnore] + public override Rectangle BoundingBox + { + get + { + + if (this._BoundingBox == null) + { + this._BoundingBox = FetchBoundingBox(); + } + return this._BoundingBox; + } + } + + #endregion Properties + + + #region Constructors + + public Rectangle(LinearRing lineRing) : base(new LinearRing(lineRing)) + { + + + foreach (var point in lineRing.Points) + { + if (MinLongitude > point.GetLongitude()) + { + MinLongitude = point.GetLongitude(); + } + + if (MaxLongitude < point.GetLongitude()) + { + MaxLongitude = point.GetLongitude(); + } + + if (MinLatitude > point.GetLatitude()) + { + MinLatitude = point.GetLatitude(); + } + + if (MaxLatitude < point.GetLatitude()) + { + MaxLatitude = point.GetLatitude(); + } + } + + var LL = new Point(new Coordinate(MinLongitude, MinLatitude)); + + var LR = new Point(new Coordinate(MaxLongitude, MinLatitude)); + + var UL = new Point(new Coordinate(MinLongitude, MaxLatitude)); + + var UR = new Point(new Coordinate(MaxLongitude, MaxLatitude)); + + this.LL = LL; + + this.LR = LR; + + this.UL = UL; + + this.UR = UR; + + } + + + public Rectangle(Point LL, Point LR, Point UL, Point UR): this(new LinearRing(new List { UL.Coordinates, + UR.Coordinates, + LR.Coordinates, + LL.Coordinates, + UL.Coordinates })) + { + + } + + + public Rectangle(Coordinate LL, Coordinate LR, Coordinate UL, Coordinate UR) : this(LL.ToPoint(), LR.ToPoint(), UL.ToPoint(), UR.ToPoint()) + { + + } + + + #endregion Constructors + + + #region Topographic Operations + + public new Rectangle FetchBoundingBox() + { + return this; + } + + public IEnumerable FetchCoordinates() + { + + var list = new List { this.UL, this.UR, this.LR, this.LL}; + + foreach (var p in list) + { + yield return p; + } + } + + /// + /// Verifies whether the other Rectangle boundaries are within this one. + /// + /// + /// + /// + public bool WithinBoundaries(Rectangle other) + { + if (this.MinLatitude <= other.MinLatitude && + this.MaxLatitude >= other.MaxLatitude && + this.MinLongitude <= other.MinLongitude && + this.MaxLongitude >= other.MaxLongitude + ) + { + return true; + } + + else + { + return false; + } + } + + + public bool Contains(Point point) + { + if (this.MaxLatitude >= point.GetLatitude() && + + this.MinLatitude <= point.GetLatitude() && + + this.MaxLongitude >= point.GetLongitude() && + + this.MinLongitude <= point.GetLongitude() ) { return true; } + + return false; + } + + #endregion Topographic Operations + + #region Equality Evaluators + + public bool Equals(Rectangle other) + { + return (this.MinLatitude == other.MinLatitude && + this.MaxLatitude == other.MaxLatitude && + this.MinLongitude == other.MinLongitude && + this.MaxLongitude == other.MaxLongitude + ); + } + + public override bool Equals(object obj) + { + return Equals(obj as Rectangle); + } + + public override int GetHashCode() + { + return Tuple.Create(this.Points.ToList()).GetHashCode(); + } + + #endregion Equality Evaluators + + + } + + + internal class BBoxEnumerator : IEnumerator, IDisposable + { + + #region Proprieties + public int Position { get; private set; } = 0; + + public List Coordinates { get; private set; } + + #endregion Proprieties + + public BBoxEnumerator(Rectangle rectangle) + { + Coordinates = new List { rectangle.UL, rectangle.UR, rectangle.LR, rectangle.LL }; + } + + public Point Current() + { + return this.Coordinates[Position]; + } + + object IEnumerator.Current + { + get + { + return Current(); + } + } + + + public void Dispose() + { + // Suppress finalization. + GC.SuppressFinalize(this); + + } + + public bool MoveNext() + { + this.Position++; + + if (this.Position < Coordinates.Count) + { + return true; + } + else + { + return false; + } + } + + public void Reset() + { + this.Position = 0; + } + } + +} diff --git a/GeoJSON/Serde/FeatureCollectionConverter.cs b/GeoJSON/Serde/FeatureCollectionConverter.cs new file mode 100644 index 0000000..46f8881 --- /dev/null +++ b/GeoJSON/Serde/FeatureCollectionConverter.cs @@ -0,0 +1,47 @@ +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BAMCIS.GeoJSON.Serde +{ + + class FeatureCollectionConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + // this converter can be applied to any type + return true; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var token = JObject.Load(reader); + + + var features = token.GetValue("features", + StringComparison.OrdinalIgnoreCase) + .ToObject>(serializer) + .ToList(); + + return new FeatureCollection(features); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var featCol = (FeatureCollection) value; + + // Aqui há um erro + JToken.FromObject(new + { + type = featCol.Type, + features = featCol.Features + }).WriteTo(writer); + } + } + + +} diff --git a/GeoJSON/Serde/FeatureIdConverter.cs b/GeoJSON/Serde/FeatureIdConverter.cs index 00491f9..7658e3e 100644 --- a/GeoJSON/Serde/FeatureIdConverter.cs +++ b/GeoJSON/Serde/FeatureIdConverter.cs @@ -21,7 +21,7 @@ public class FeatureIdConverter : JsonConverter public override bool CanConvert(Type objectType) { - return objectType == typeof(Position); + return objectType == typeof(Coordinate); } /// diff --git a/GeoJSON/Serde/LineStringConverter.cs b/GeoJSON/Serde/LineStringConverter.cs new file mode 100644 index 0000000..f8c582a --- /dev/null +++ b/GeoJSON/Serde/LineStringConverter.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BAMCIS.GeoJSON.Serde +{ + public class LineStringConverter : JsonConverter + { + #region Public Properties + + public override bool CanRead => true; + + public override bool CanWrite => true; + + #endregion + + #region Public Methods + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(MultiLineString); + } + + /// + /// This takes the array of arrays and recasts them back to line string objects + /// + /// + /// + /// + /// + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject token = JObject.Load(reader); + + var coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>(serializer); + + var lineString = new LineString(coordinates.Select(c => c.ToPoint())); + + return lineString; + } + + /// + /// This flattens the coordinates property into an array of arrays + /// + /// + /// + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var lineString = (LineString) value; + + JToken.FromObject(new + { + type = lineString.Type, + coordinates = lineString.Points.Select(p => p.Coordinates).ToList() + }).WriteTo(writer); + } + + #endregion Public Methods + } +} diff --git a/GeoJSON/Serde/LinearRingConverter.cs b/GeoJSON/Serde/LinearRingConverter.cs new file mode 100644 index 0000000..07c12ab --- /dev/null +++ b/GeoJSON/Serde/LinearRingConverter.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BAMCIS.GeoJSON.Serde +{ + public class LinearRingConverter : JsonConverter + { + #region Public Properties + + public override bool CanRead => true; + + public override bool CanWrite => true; + + #endregion + + #region Public Methods + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(MultiLineString); + } + + /// + /// This takes the array of arrays and recasts them back to line string objects + /// + /// + /// + /// + /// + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject token = JObject.Load(reader); + + var coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>(serializer); + + var lineString = new LinearRing(coordinates); + + return lineString; + } + + /// + /// This flattens the coordinates property into an array of arrays + /// + /// + /// + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var lineRing = (LinearRing) value; + + JToken.FromObject(new + { + type = lineRing.Type, + coordinates = lineRing.Points.Select(p => p.Coordinates).ToList() + }).WriteTo(writer); + } + + #endregion Public Methods + } +} diff --git a/GeoJSON/Serde/MultiLineStringConverter.cs b/GeoJSON/Serde/MultiLineStringConverter.cs index d87ab14..1b5ff6b 100644 --- a/GeoJSON/Serde/MultiLineStringConverter.cs +++ b/GeoJSON/Serde/MultiLineStringConverter.cs @@ -38,9 +38,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist { JObject token = JObject.Load(reader); - IEnumerable> coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>(serializer); + IEnumerable> coordinates = token.GetValue("Coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>(serializer); - return new MultiLineString(coordinates.Select(x => new LineString(x))); + List lineStrings = coordinates.Select(c => LineSegment.CoordinatesToLineString(c)).ToList(); + + return new MultiLineString(lineStrings); } /// @@ -56,7 +58,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s JToken.FromObject(new { type = mls.Type, - coordinates = mls.Coordinates.Select(x => x.Coordinates) + coordinates = mls.LineStrings.Select(x => x.Points.Select(p => p.Coordinates)) }).WriteTo(writer); } diff --git a/GeoJSON/Serde/MultiPointConverter.cs b/GeoJSON/Serde/MultiPointConverter.cs new file mode 100644 index 0000000..1fac12c --- /dev/null +++ b/GeoJSON/Serde/MultiPointConverter.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BAMCIS.GeoJSON.Serde +{ + class MultiPointConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + // this converter can be applied to any type + return true; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var token = JObject.Load(reader); + + + var points = token.GetValue("points", + StringComparison.OrdinalIgnoreCase) + .ToObject>(serializer) + .ToList(); + + // Take this array of arrays of arrays and create linear rings + // and use those to create create polygons + return new MultiPoint(points.Select(c => c.ToPoint()).ToList()); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var multiPoint = (MultiPoint) value; + + var coordinates = multiPoint.Points.Select(p => p.Coordinates).ToList(); + + JToken.FromObject(new + { + type = multiPoint.Type, + points = coordinates + }).WriteTo(writer); + } + } +} diff --git a/GeoJSON/Serde/MultiPolygonConverter.cs b/GeoJSON/Serde/MultiPolygonConverter.cs index 1406780..a7df2f0 100644 --- a/GeoJSON/Serde/MultiPolygonConverter.cs +++ b/GeoJSON/Serde/MultiPolygonConverter.cs @@ -38,17 +38,14 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist { JObject token = JObject.Load(reader); - IEnumerable>> coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>>(serializer); + var polygonsCoordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>>(serializer); // Take this array of arrays of arrays and create linear rings // and use those to create create polygons - return new MultiPolygon( - coordinates - .Select(x => new Polygon( - x.Select(y => new LinearRing(y)) - ) - ) - ); + + var polygons = polygonsCoordinates.Select(linearRings => new Polygon(linearRings.Select(linearRingCoordinates => new LinearRing(linearRingCoordinates)))); + + return new MultiPolygon(polygons); } /// @@ -64,7 +61,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s JToken.FromObject(new { type = mp.Type, - coordinates = mp.Coordinates.Select(x => x.Coordinates.Select(y => y.Coordinates)) + coordinates = mp.Polygons.Select(polygon => polygon.LinearRings.Select(linearRing => linearRing.Points.Select(p => p.Coordinates))) }).WriteTo(writer); } diff --git a/GeoJSON/Serde/PointConverter.cs b/GeoJSON/Serde/PointConverter.cs new file mode 100644 index 0000000..2769651 --- /dev/null +++ b/GeoJSON/Serde/PointConverter.cs @@ -0,0 +1,70 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +namespace BAMCIS.GeoJSON.Serde +{ + /// + /// Converts a position to an array of coordinates and back + /// + public class PointConverter : JsonConverter + { + #region Public Properties + + public override bool CanRead => true; + + public override bool CanWrite => true; + + #endregion + + #region Public Methods + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(PointConverter); + } + + /// + /// Reads an array of doubles and creates a Position object + /// + /// + /// + /// + /// + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject token = JObject.Load(reader); + + Coordinate coordinate = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase) + .ToObject(serializer); + + var point = new Point(coordinate); + + return point; + + } + + /// + /// Takes the position object and converts it to an array of doubles + /// + /// + /// + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + Point point = (Point) value; + + JToken.FromObject(new + { + type = point.Type, + coordinates = point.Coordinates + }).WriteTo(writer); + } + + #endregion + } +} diff --git a/GeoJSON/Serde/PolygonConverter.cs b/GeoJSON/Serde/PolygonConverter.cs index 2a51fa0..cdde5c1 100644 --- a/GeoJSON/Serde/PolygonConverter.cs +++ b/GeoJSON/Serde/PolygonConverter.cs @@ -38,13 +38,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist { JObject token = JObject.Load(reader); - IEnumerable> coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>(serializer); + IEnumerable> coordinatesPerLinearRing = token.GetValue("Coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>(serializer); // Take this array of arrays of arrays and create linear rings // and use those to create create polygons - return new Polygon( - coordinates.Select(x => new LinearRing(x)) - ); + return new Polygon(coordinatesPerLinearRing.Select(coordinates => new LinearRing(coordinates))); } /// @@ -57,10 +55,12 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s { Polygon poly = (Polygon)value; + var coordinates = poly.LinearRings.Select(lineRing => lineRing.Points.Select(p => p.Coordinates)); + JToken.FromObject(new { type = poly.Type, - coordinates = poly.Coordinates.Select(x => x.Coordinates) + coordinates = coordinates }).WriteTo(writer); } diff --git a/GeoJSON/Serde/PositionConverter.cs b/GeoJSON/Serde/PositionConverter.cs index 54bdd3d..8280a6e 100644 --- a/GeoJSON/Serde/PositionConverter.cs +++ b/GeoJSON/Serde/PositionConverter.cs @@ -22,7 +22,7 @@ public class PositionConverter : JsonConverter public override bool CanConvert(Type objectType) { - return objectType == typeof(Position); + return objectType == typeof(Coordinate); } /// @@ -47,7 +47,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist elevation = token.ElementAt(2).ToObject(serializer); } - return new Position(longitude, latitude, elevation); + return new Coordinate(longitude, latitude, elevation); } /// @@ -58,7 +58,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - Position pos = (Position)value; + Coordinate pos = (Coordinate)value; if (pos.HasElevation()) { diff --git a/GeoJSON/Wkb/EndianAwareBinaryReader.cs b/GeoJSON/Wkb/EndianAwareBinaryReader.cs index aed1c70..7877140 100644 --- a/GeoJSON/Wkb/EndianAwareBinaryReader.cs +++ b/GeoJSON/Wkb/EndianAwareBinaryReader.cs @@ -101,7 +101,7 @@ public double ReadDouble(Endianness endianness) return BitConverter.ToDouble(temp, 0); } - catch (Exception e) + catch (Exception) { return 0; } @@ -129,7 +129,7 @@ private byte[] ReadForEndianness(int bytesToRead, Endianness endianness) return bytesRead; } - catch (Exception e) + catch (Exception) { return null; } diff --git a/GeoJSON/Wkb/EndianAwareBinaryWriter.cs b/GeoJSON/Wkb/EndianAwareBinaryWriter.cs index 7c60b74..30f45eb 100644 --- a/GeoJSON/Wkb/EndianAwareBinaryWriter.cs +++ b/GeoJSON/Wkb/EndianAwareBinaryWriter.cs @@ -102,9 +102,9 @@ public EndianAwareBinaryWriter(Stream input, Encoding encoding, bool leaveOpen, public void Write(UInt64 value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); - public void Write(bool value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + public void Write(bool value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes((bool)value), endianness); - public void Write(sbyte value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + public void Write(sbyte value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value.ToString()[0] ), endianness); public void WriteEndianness() => WriteEndianness(this._endianness); diff --git a/GeoJSON/Wkb/WkbConverter.cs b/GeoJSON/Wkb/WkbConverter.cs index 10aab44..5c28bfc 100644 --- a/GeoJSON/Wkb/WkbConverter.cs +++ b/GeoJSON/Wkb/WkbConverter.cs @@ -108,24 +108,27 @@ private static Geometry FromBinary(EndianAwareBinaryReader reader) } default: { - throw new NotSupportedException($"Unsupported WKB type {type.ToString()}."); + throw new NotSupportedException($"Unsupported WKB type {type}."); } } } private static Point PointFrom(EndianAwareBinaryReader reader) { - return new Point(new Position(reader.ReadDouble(), reader.ReadDouble())); + return new Point(new Coordinate(reader.ReadDouble(), reader.ReadDouble())); } private static LineString LineStringFrom(EndianAwareBinaryReader reader) { UInt32 amount = reader.ReadUInt32(); - List coordinates = new List(); + List coordinates = new List(); for (int i = 0; i < amount; i++) { - coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); + var pos = new Coordinate(reader.ReadDouble(), + reader.ReadDouble()); + + coordinates.Add(new Point(pos)); } return new LineString(coordinates); @@ -134,18 +137,20 @@ private static LineString LineStringFrom(EndianAwareBinaryReader reader) private static Polygon PolygonFrom(EndianAwareBinaryReader reader) { int ringQuantity = reader.ReadInt32(); + List positions = new List(ringQuantity); List rings = new List(ringQuantity); - for (int i = 0; i < ringQuantity; i++) { int numberOfPositions = reader.ReadInt32(); - List coordinates = new List(numberOfPositions); + List coordinates = new List(numberOfPositions); for (int j = 0; j < numberOfPositions; j++) { - coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); + coordinates.Add(new Coordinate(reader.ReadDouble(), reader.ReadDouble())); } - rings.Add(new LinearRing(coordinates)); + var ring = new LinearRing(coordinates); + + rings.Add(ring); } return new Polygon(rings); @@ -153,7 +158,7 @@ private static Polygon PolygonFrom(EndianAwareBinaryReader reader) private static MultiPoint MultiPointFrom(EndianAwareBinaryReader reader) { - List coordinates = new List(); + List coordinates = new List(); int numberOfGroups = reader.ReadInt32(); for (int i = 0; i < numberOfGroups; i++) @@ -164,7 +169,7 @@ private static MultiPoint MultiPointFrom(EndianAwareBinaryReader reader) for (int j = 0; j < numberOfPositions; j++) { - coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); + coordinates.Add(new Coordinate(reader.ReadDouble(), reader.ReadDouble())); } } @@ -237,11 +242,12 @@ private static void ToBinary(EndianAwareBinaryWriter writer, LineString lineStri { writer.WriteEndianness(); writer.Write((UInt32)WkbType.LineString); - writer.Write(lineString.Coordinates.Count()); + var points = lineString.Points; + writer.Write(points.Count()); - foreach (Position pos in lineString.Coordinates) + foreach (var point in points) { - WritePosition(writer, pos); + WritePosition(writer, point.Coordinates); } } @@ -249,15 +255,16 @@ private static void ToBinary(EndianAwareBinaryWriter writer, Polygon polygon) { writer.WriteEndianness(); writer.Write((UInt32)WkbType.Polygon); - writer.Write((UInt32)polygon.Coordinates.Count()); + + writer.Write((UInt32) polygon.LinearRings.Count()); - foreach (LinearRing linearRing in polygon.Coordinates) + foreach (LinearRing linearRing in polygon.LinearRings) { - writer.Write((UInt32)linearRing.Coordinates.Count()); + writer.Write((UInt32) linearRing.Points.Count()); - foreach (Position pos in linearRing.Coordinates) + foreach (Point pos in linearRing.Points) { - WritePosition(writer, pos); + WritePosition(writer, pos.Coordinates); } } } @@ -266,11 +273,10 @@ private static void ToBinary(EndianAwareBinaryWriter writer, MultiPoint multiPoi { writer.WriteEndianness(); writer.Write((UInt32)WkbType.MultiPoint); - writer.Write((UInt32)multiPoint.Coordinates.Count()); + writer.Write((UInt32)multiPoint.Points.Count()); - foreach (Position pos in multiPoint.Coordinates) + foreach (Point point in multiPoint.Points) { - Point point = new Point(pos); ToBinary(writer, point); } } @@ -279,9 +285,9 @@ private static void ToBinary(EndianAwareBinaryWriter writer, MultiLineString mul { writer.WriteEndianness(); writer.Write((UInt32)WkbType.MultiLineString); - writer.Write((UInt32)multiLineString.Coordinates.Count()); + writer.Write((UInt32)multiLineString.LineStrings.Count()); - foreach (LineString lineString in multiLineString.Coordinates) + foreach (LineString lineString in multiLineString.LineStrings) { ToBinary(writer, lineString); } @@ -291,9 +297,9 @@ private static void ToBinary(EndianAwareBinaryWriter writer, MultiPolygon multiP { writer.WriteEndianness(); writer.Write((UInt32)WkbType.MultiPolygon); - writer.Write((UInt32)multiPolygon.Coordinates.Count()); + writer.Write((UInt32)multiPolygon.Polygons.Count()); - foreach (Polygon polygon in multiPolygon.Coordinates) + foreach (Polygon polygon in multiPolygon.Polygons) { ToBinary(writer, polygon); } @@ -352,12 +358,12 @@ private static void ToBinary(EndianAwareBinaryWriter writer, Geometry geometry) } default: { - throw new NotSupportedException($"The GeoJson type {geometry.Type.ToString()} is not supported for conversion to WKB."); + throw new NotSupportedException($"The GeoJson type {geometry.Type} is not supported for conversion to WKB."); } } } - private static EndianAwareBinaryWriter WritePosition(EndianAwareBinaryWriter writer, Position position) + private static EndianAwareBinaryWriter WritePosition(EndianAwareBinaryWriter writer, Coordinate position) { writer.Write(position.Longitude); writer.Write(position.Latitude); diff --git a/README.md b/README.md index 582ba1b..9e62244 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,28 @@ byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000" Point point = WkbConverter.FromBinary(bytes); ``` + +## Topological Operations + +### Example 1 + +```csharp + var point = new Point(5, 2); + + var coordinates = new List { new Coordinate(1, 1) , + new Coordinate(1, 4) , + new Coordinate(4, 4) , + new Coordinate(4, 1) , + new Coordinate(1, 1) + }; + var linearRing = new LinearRing(coordinates); + + var polygon = new Polygon(linearRing); + + + regularPolygon.Contains(point) +``` + ### Usage Notes Each of the 9 GeoJSON types: **Feature**, **FeatureCollection**, **GeometryCollection**, **LineString**, **MultiLineString**, **MultiPoint**, **MultiPolygon**, **Point**, and **Polygon** all have convenience methods ToJson() and FromJson() to make serialization and deserialization easy.