Index: Core/Components/src/Core.Components.DotSpatial/Converter/FeatureBasedMapDataConverter.cs =================================================================== diff -u -r83ac738ad69ef53eba6ca4f647657e5b7f024b96 -rf254b26cf9d6d3f6a172e1613740c44f2da70d3c --- Core/Components/src/Core.Components.DotSpatial/Converter/FeatureBasedMapDataConverter.cs (.../FeatureBasedMapDataConverter.cs) (revision 83ac738ad69ef53eba6ca4f647657e5b7f024b96) +++ Core/Components/src/Core.Components.DotSpatial/Converter/FeatureBasedMapDataConverter.cs (.../FeatureBasedMapDataConverter.cs) (revision f254b26cf9d6d3f6a172e1613740c44f2da70d3c) @@ -99,7 +99,15 @@ layer.Name = data.Name; layer.ShowLabels = data.ShowLabels; ((IMapFeatureLayer) layer).LabelLayer = GetLabelLayer(GetAttributeMapping(data), layer.DataSet, data.SelectedMetaDataAttribute); - layer.Symbolizer = CreateSymbolizer(data); + + if (data.MapTheme == null) + { + layer.Symbolizer = CreateSymbolizer(data); + } + else + { + layer.Symbology = CreateScheme(data); + } } /// @@ -119,6 +127,11 @@ /// Null should never be returned as this will break DotSpatial. protected abstract IFeatureSymbolizer CreateSymbolizer(TFeatureBasedMapData mapData); + protected virtual IFeatureScheme CreateScheme(TFeatureBasedMapData mapData) + { + return null; + } + /// /// Converts an of to an /// of . @@ -178,7 +191,7 @@ /// This method is used for obtaining a mapping between map data attribute names and DotSpatial /// attribute names. This mapping is needed because DotSpatial can't handle special characters. /// - private static Dictionary GetAttributeMapping(TFeatureBasedMapData data) + protected static Dictionary GetAttributeMapping(TFeatureBasedMapData data) { return Enumerable.Range(0, data.MetaData.Count()) .ToDictionary(md => data.MetaData.ElementAt(md), mdi => mdi + 1); Index: Core/Components/src/Core.Components.DotSpatial/Converter/MapPointDataConverter.cs =================================================================== diff -u -r83ac738ad69ef53eba6ca4f647657e5b7f024b96 -rf254b26cf9d6d3f6a172e1613740c44f2da70d3c --- Core/Components/src/Core.Components.DotSpatial/Converter/MapPointDataConverter.cs (.../MapPointDataConverter.cs) (revision 83ac738ad69ef53eba6ca4f647657e5b7f024b96) +++ Core/Components/src/Core.Components.DotSpatial/Converter/MapPointDataConverter.cs (.../MapPointDataConverter.cs) (revision f254b26cf9d6d3f6a172e1613740c44f2da70d3c) @@ -19,10 +19,13 @@ // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. +using System; using System.Collections.Generic; using System.Linq; using Core.Components.Gis.Data; using Core.Components.Gis.Features; +using Core.Components.Gis.Theme; +using Core.Components.Gis.Theme.Criteria; using DotSpatial.Controls; using DotSpatial.Data; using DotSpatial.Symbology; @@ -42,11 +45,77 @@ protected override IFeatureSymbolizer CreateSymbolizer(MapPointData mapData) { + return CreatePointSymbolizer(mapData); + } + + protected override IFeatureScheme CreateScheme(MapPointData mapData) + { + PointSymbolizer symbolizer = CreatePointSymbolizer(mapData); + + IFeatureScheme scheme = new PointScheme(); + scheme.ClearCategories(); + scheme.AddCategory(new PointCategory(symbolizer)); + + MapTheme mapTheme = mapData.MapTheme; + Dictionary attributeMapping = GetAttributeMapping(mapData); + int attributeIndex = attributeMapping[mapTheme.AttributeName]; + foreach (CategoryTheme categoryTheme in mapTheme.CategoryThemes) + { + var category = new PointCategory(CreatePointSymbolizer(mapData)); + category.SetColor(categoryTheme.Color); + category.FilterExpression = CreateFilterExpression(attributeIndex, categoryTheme.Criteria); + scheme.AddCategory(category); + } + + return scheme; + } + + private static PointSymbolizer CreatePointSymbolizer(MapPointData mapData) + { var symbolizer = new PointSymbolizer(mapData.Style.Color, MapDataHelper.Convert(mapData.Style.Symbol), mapData.Style.Size); symbolizer.SetOutline(mapData.Style.StrokeColor, mapData.Style.StrokeThickness); return symbolizer; } + private static string CreateFilterExpression(int attributeIndex, ICriteria criteria) + { + var valueCriteria = criteria as ValueCriteria; + if (valueCriteria != null) + { + ValueCriteriaOperator valueOperator = valueCriteria.ValueOperator; + switch (valueOperator) + { + case ValueCriteriaOperator.EqualValue: + return $"[{attributeIndex}] = {valueCriteria.Value}"; + case ValueCriteriaOperator.UnequalValue: + return $"[{attributeIndex}] != {valueCriteria.Value}"; + default: + throw new NotSupportedException($"The enum value {nameof(ValueCriteriaOperator)}.{valueOperator} is not supported."); + } + } + + var rangeCriteria = criteria as RangeCriteria; + if (rangeCriteria != null) + { + RangeCriteriaOperator rangeCriteriaOperator = rangeCriteria.RangeCriteriaOperator; + switch (rangeCriteriaOperator) + { + case RangeCriteriaOperator.AllBoundsInclusive: + return $"[{attributeIndex}] >= {rangeCriteria.LowerBound} AND [{attributeIndex}] <= {rangeCriteria.UpperBound}"; + case RangeCriteriaOperator.LowerBoundInclusive: + return $"[{attributeIndex}] >= {rangeCriteria.LowerBound} AND [{attributeIndex}] < {rangeCriteria.UpperBound}"; + case RangeCriteriaOperator.UpperBoundInclusive: + return $"[{attributeIndex}] > {rangeCriteria.LowerBound} AND [{attributeIndex}] <= {rangeCriteria.UpperBound}"; + case RangeCriteriaOperator.AllBoundsExclusive: + return $"[{attributeIndex}] > {rangeCriteria.LowerBound} AND [{attributeIndex}] < {rangeCriteria.UpperBound}"; + default: + throw new NotSupportedException($"The enum value {nameof(RangeCriteriaOperator)}.{rangeCriteriaOperator} is not supported."); + } + } + + throw new NotSupportedException($"Can't convert a {nameof(ICriteria)} of type {criteria.GetType()}"); // TODO WTI-1551: Test this exception + } + private static IEnumerable GetAllMapFeatureCoordinates(MapFeature feature) { return feature.MapGeometries.SelectMany(mapGeometry => ConvertPoint2DElementsToCoordinates(mapGeometry.PointCollections.First())).ToArray(); Index: Core/Components/test/Core.Components.DotSpatial.Test/Converter/MapPointDataConverterTest.cs =================================================================== diff -u -r66964d0c53eb0d904f7ada3e3bbc1e9604e241e7 -rf254b26cf9d6d3f6a172e1613740c44f2da70d3c --- Core/Components/test/Core.Components.DotSpatial.Test/Converter/MapPointDataConverterTest.cs (.../MapPointDataConverterTest.cs) (revision 66964d0c53eb0d904f7ada3e3bbc1e9604e241e7) +++ Core/Components/test/Core.Components.DotSpatial.Test/Converter/MapPointDataConverterTest.cs (.../MapPointDataConverterTest.cs) (revision f254b26cf9d6d3f6a172e1613740c44f2da70d3c) @@ -19,6 +19,7 @@ // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -28,6 +29,8 @@ using Core.Components.Gis.Features; using Core.Components.Gis.Geometries; using Core.Components.Gis.Style; +using Core.Components.Gis.Theme; +using Core.Components.Gis.Theme.Criteria; using DotSpatial.Controls; using DotSpatial.Data; using DotSpatial.Symbology; @@ -150,9 +153,11 @@ [Test] [Combinatorial] public void ConvertLayerProperties_MapPointDataWithStyle_ConvertsStyleToMapPointLayer( - [Values(KnownColor.AliceBlue, KnownColor.Azure)] KnownColor color, + [Values(KnownColor.AliceBlue, KnownColor.Azure)] + KnownColor color, [Values(1, 5)] int width, - [Values(PointSymbol.Circle, PointSymbol.Square)] PointSymbol pointStyle) + [Values(PointSymbol.Circle, PointSymbol.Square)] + PointSymbol pointStyle) { // Setup var converter = new MapPointDataConverter(); @@ -181,6 +186,208 @@ AssertAreEqual(expectedSymbolizer, mapPointLayer.Symbolizer); } + [Test] + public void ConvertLayerProperties_MapPointDataWithStyleAndValueCriteria_ConvertDataToMapPointLayer() + { + // Setup + const string metadataAttribute = "Meta"; + var random = new Random(21); + + var unequalCriteria = new ValueCriteria(ValueCriteriaOperator.UnequalValue, + random.NextDouble()); + var equalCriteria = new ValueCriteria(ValueCriteriaOperator.EqualValue, + random.NextDouble()); + var theme = new MapTheme(metadataAttribute, new[] + { + new CategoryTheme(Color.FromKnownColor(random.NextEnum()), + equalCriteria), + new CategoryTheme(Color.FromKnownColor(random.NextEnum()), + unequalCriteria) + }); + + var pointStyle = new PointStyle + { + Color = Color.FromKnownColor(random.NextEnum()), + Size = random.Next(1, 48), + Symbol = PointSymbol.Circle, + StrokeColor = Color.FromKnownColor(random.NextEnum()), + StrokeThickness = random.Next(1, 48) + }; + var mapPointData = new MapPointData("test", pointStyle) + { + Features = new[] + { + CreateMapFeatureWithMetaData(metadataAttribute) + }, + MapTheme = theme + }; + + var mapPointLayer = new MapPointLayer(); + + var converter = new MapPointDataConverter(); + + // Call + converter.ConvertLayerProperties(mapPointData, mapPointLayer); + + // Assert + const PointShape expectedPointShape = PointShape.Ellipse; + PointSymbolizer expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + pointStyle.Color); + + IPointScheme appliedScheme = mapPointLayer.Symbology; + Assert.AreEqual(3, appliedScheme.Categories.Count); + + IPointCategory baseCategory = appliedScheme.Categories[0]; + AssertAreEqual(expectedSymbolizer, baseCategory.Symbolizer); + Assert.IsNull(baseCategory.FilterExpression); + + IPointCategory equalSchemeCategory = appliedScheme.Categories[1]; + string expectedFilter = $"[1] = {equalCriteria.Value}"; + Assert.AreEqual(expectedFilter, equalSchemeCategory.FilterExpression); + expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + theme.CategoryThemes.ElementAt(0).Color); + AssertAreEqual(expectedSymbolizer, equalSchemeCategory.Symbolizer); + + IPointCategory unEqualSchemeCategory = appliedScheme.Categories[2]; + expectedFilter = $"[1] != {unequalCriteria.Value}"; + Assert.AreEqual(expectedFilter, unEqualSchemeCategory.FilterExpression); + expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + theme.CategoryThemes.ElementAt(1).Color); + AssertAreEqual(expectedSymbolizer, unEqualSchemeCategory.Symbolizer); + } + + private static PointSymbolizer CreateExpectedSymbolizer(PointStyle expectedPointStyle, + PointShape expectedPointShape, + Color expectedColor) + { + var expectedSymbolizer = new PointSymbolizer(expectedColor, expectedPointShape, expectedPointStyle.Size); + expectedSymbolizer.SetOutline(expectedPointStyle.StrokeColor, expectedPointStyle.StrokeThickness); + + return expectedSymbolizer; + } + + [Test] + public void ConvertLayerProperties_MapPointDataWithStyleAndRangeCriteria_ConvertDataToMapPointLayer() + { + // Setup + const string metadataAttribute = "Meta"; + var random = new Random(21); + + var allBoundsInclusiveCriteria = new RangeCriteria(RangeCriteriaOperator.AllBoundsInclusive, + random.NextDouble(), + 1 + random.NextDouble()); + var lowerBoundInclusiveCriteria = new RangeCriteria(RangeCriteriaOperator.LowerBoundInclusive, + random.NextDouble(), + 1 + random.NextDouble()); + var upperBoundInclusiveCriteria = new RangeCriteria(RangeCriteriaOperator.UpperBoundInclusive, + random.NextDouble(), + 1 + random.NextDouble()); + var allBoundsExclusiveCriteria = new RangeCriteria(RangeCriteriaOperator.AllBoundsExclusive, + random.NextDouble(), + 1 + random.NextDouble()); + var theme = new MapTheme(metadataAttribute, new[] + { + new CategoryTheme(Color.FromKnownColor(random.NextEnum()), + allBoundsInclusiveCriteria), + new CategoryTheme(Color.FromKnownColor(random.NextEnum()), + lowerBoundInclusiveCriteria), + new CategoryTheme(Color.FromKnownColor(random.NextEnum()), + upperBoundInclusiveCriteria), + new CategoryTheme(Color.FromKnownColor(random.NextEnum()), + allBoundsExclusiveCriteria) + }); + + var pointStyle = new PointStyle + { + Color = Color.FromKnownColor(random.NextEnum()), + Size = random.Next(1, 48), + Symbol = PointSymbol.Circle, + StrokeColor = Color.FromKnownColor(random.NextEnum()), + StrokeThickness = random.Next(1, 48) + }; + var mapPointData = new MapPointData("test", pointStyle) + { + Features = new[] + { + CreateMapFeatureWithMetaData(metadataAttribute) + }, + MapTheme = theme + }; + + var mapPointLayer = new MapPointLayer(); + + var converter = new MapPointDataConverter(); + + // Call + converter.ConvertLayerProperties(mapPointData, mapPointLayer); + + // Assert + const PointShape expectedPointShape = PointShape.Ellipse; + PointSymbolizer expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + pointStyle.Color); + + IPointScheme appliedScheme = mapPointLayer.Symbology; + Assert.AreEqual(5, appliedScheme.Categories.Count); + + IPointCategory baseCategory = appliedScheme.Categories[0]; + AssertAreEqual(expectedSymbolizer, baseCategory.Symbolizer); + Assert.IsNull(baseCategory.FilterExpression); + + IPointCategory allBoundsInclusiveCategory = appliedScheme.Categories[1]; + string expectedFilter = $"[1] >= {allBoundsInclusiveCriteria.LowerBound} AND [1] <= {allBoundsInclusiveCriteria.UpperBound}"; + Assert.AreEqual(expectedFilter, allBoundsInclusiveCategory.FilterExpression); + expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + theme.CategoryThemes.ElementAt(0).Color); + AssertAreEqual(expectedSymbolizer, allBoundsInclusiveCategory.Symbolizer); + + IPointCategory lowerBoundInclusiveCategory = appliedScheme.Categories[2]; + expectedFilter = $"[1] >= {lowerBoundInclusiveCriteria.LowerBound} AND [1] < {lowerBoundInclusiveCriteria.UpperBound}"; + Assert.AreEqual(expectedFilter, lowerBoundInclusiveCategory.FilterExpression); + expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + theme.CategoryThemes.ElementAt(1).Color); + AssertAreEqual(expectedSymbolizer, lowerBoundInclusiveCategory.Symbolizer); + + IPointCategory upperBoundInclusiveCategory = appliedScheme.Categories[3]; + expectedFilter = $"[1] > {upperBoundInclusiveCriteria.LowerBound} AND [1] <= {upperBoundInclusiveCriteria.UpperBound}"; + Assert.AreEqual(expectedFilter, upperBoundInclusiveCategory.FilterExpression); + expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + theme.CategoryThemes.ElementAt(2).Color); + AssertAreEqual(expectedSymbolizer, upperBoundInclusiveCategory.Symbolizer); + + IPointCategory allBoundsExclusiveCategory = appliedScheme.Categories[4]; + expectedFilter = $"[1] > {allBoundsExclusiveCriteria.LowerBound} AND [1] < {allBoundsExclusiveCriteria.UpperBound}"; + Assert.AreEqual(expectedFilter, allBoundsExclusiveCategory.FilterExpression); + expectedSymbolizer = CreateExpectedSymbolizer(pointStyle, + expectedPointShape, + theme.CategoryThemes.ElementAt(3).Color); + AssertAreEqual(expectedSymbolizer, allBoundsExclusiveCategory.Symbolizer); + } + + private static MapFeature CreateMapFeatureWithMetaData(string metadataAttributeName) + { + var random = new Random(21); + var mapFeature = new MapFeature(new[] + { + new MapGeometry(new[] + { + new[] + { + new Point2D(random.NextDouble(), random.NextDouble()) + } + }) + }); + mapFeature.MetaData[metadataAttributeName] = new object(); + + return mapFeature; + } + private static void AssertAreEqual(IPointSymbolizer expectedSymbolizer, IPointSymbolizer actualSymbolizer) { IList firstSymbols = expectedSymbolizer.Symbols; Index: Core/Components/test/Core.Components.DotSpatial.Test/Core.Components.DotSpatial.Test.csproj =================================================================== diff -u -rac96d7c315129af851634ed5a4a6800b59ede718 -rf254b26cf9d6d3f6a172e1613740c44f2da70d3c --- Core/Components/test/Core.Components.DotSpatial.Test/Core.Components.DotSpatial.Test.csproj (.../Core.Components.DotSpatial.Test.csproj) (revision ac96d7c315129af851634ed5a4a6800b59ede718) +++ Core/Components/test/Core.Components.DotSpatial.Test/Core.Components.DotSpatial.Test.csproj (.../Core.Components.DotSpatial.Test.csproj) (revision f254b26cf9d6d3f6a172e1613740c44f2da70d3c) @@ -27,6 +27,10 @@ False ..\..\..\..\lib\DotSpatial.1.8\DotSpatial.Projections.dll + + False + ..\..\..\..\lib\DotSpatial.1.8\DotSpatial.Serialization.dll + False ..\..\..\..\lib\DotSpatial.1.8\DotSpatial.Symbology.dll @@ -104,7 +108,7 @@ Core.Components.DotSpatial - {318ba582-88c9-4816-a54a-a7e431461de3} + {318BA582-88C9-4816-A54A-A7E431461DE3} Core.Components.Gis