├── .gitattributes ├── .gitignore ├── README.md ├── bin ├── Simplifynet.dll └── Simplifynet.pdb └── src ├── Simplifynet.CSharpPortable ├── ISimplifyUtility.cs ├── Point.cs ├── Properties │ └── AssemblyInfo.cs ├── SimplifyUtility.cs ├── SimplifyUtility3D.cs └── Simplifynet.CSharpPortable.csproj ├── Simplifynet.Tests ├── Properties │ └── AssemblyInfo.cs ├── SimplifyUtilityTests.cs ├── Simplifynet.Tests.csproj ├── data │ └── LongLine.cs └── extentions │ └── nunit.framework.dll └── Simplifynet.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Oo]bj/ 16 | 17 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 18 | !packages/*/build/ 19 | 20 | # MSTest test Results 21 | [Tt]est[Rr]esult*/ 22 | [Bb]uild[Ll]og.* 23 | 24 | *_i.c 25 | *_p.c 26 | *.ilk 27 | *.meta 28 | *.obj 29 | *.pch 30 | *.pgc 31 | *.pgd 32 | *.rsp 33 | *.sbr 34 | *.tlb 35 | *.tli 36 | *.tlh 37 | *.tmp 38 | *.tmp_proj 39 | *.log 40 | *.vspscc 41 | *.vssscc 42 | .builds 43 | *.pidb 44 | *.log 45 | *.scc 46 | 47 | # Visual C++ cache files 48 | ipch/ 49 | *.aps 50 | *.ncb 51 | *.opensdf 52 | *.sdf 53 | *.cachefile 54 | 55 | # Visual Studio profiler 56 | *.psess 57 | *.vsp 58 | *.vspx 59 | 60 | # Guidance Automation Toolkit 61 | *.gpState 62 | 63 | # ReSharper is a .NET coding add-in 64 | _ReSharper*/ 65 | *.[Rr]e[Ss]harper 66 | 67 | # TeamCity is a build add-in 68 | _TeamCity* 69 | 70 | # DotCover is a Code Coverage Tool 71 | *.dotCover 72 | 73 | # NCrunch 74 | *.ncrunch* 75 | .*crunch*.local.xml 76 | 77 | # Installshield output folder 78 | [Ee]xpress/ 79 | 80 | # DocProject is a documentation generator add-in 81 | DocProject/buildhelp/ 82 | DocProject/Help/*.HxT 83 | DocProject/Help/*.HxC 84 | DocProject/Help/*.hhc 85 | DocProject/Help/*.hhk 86 | DocProject/Help/*.hhp 87 | DocProject/Help/Html2 88 | DocProject/Help/html 89 | 90 | # Click-Once directory 91 | publish/ 92 | 93 | # Publish Web Output 94 | *.Publish.xml 95 | 96 | # NuGet Packages Directory 97 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 98 | #packages/ 99 | 100 | # Windows Azure Build Output 101 | csx 102 | *.build.csdef 103 | 104 | # Windows Store app package directory 105 | AppPackages/ 106 | 107 | # Others 108 | sql/ 109 | *.Cache 110 | ClientBin/ 111 | [Ss]tyle[Cc]op.* 112 | ~$* 113 | *~ 114 | *.dbmdl 115 | *.[Pp]ublish.xml 116 | *.pfx 117 | *.publishsettings 118 | 119 | # RIA/Silverlight projects 120 | Generated_Code/ 121 | 122 | # Backup & report files from converting an old project file to a newer 123 | # Visual Studio version. Backup files are not needed, because we have git ;-) 124 | _UpgradeReport_Files/ 125 | Backup*/ 126 | UpgradeLog*.XML 127 | UpgradeLog*.htm 128 | 129 | # SQL Server files 130 | App_Data/*.mdf 131 | App_Data/*.ldf 132 | 133 | 134 | #LightSwitch generated files 135 | GeneratedArtifacts/ 136 | _Pvt_Extensions/ 137 | ModelManifest.xml 138 | 139 | # ========================= 140 | # Windows detritus 141 | # ========================= 142 | 143 | # Windows image file caches 144 | Thumbs.db 145 | ehthumbs.db 146 | 147 | # Folder config file 148 | Desktop.ini 149 | 150 | # Recycle Bin used on file shares 151 | $RECYCLE.BIN/ 152 | 153 | # Mac desktop service store files 154 | .DS_Store 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | simplify-net 2 | =========== 3 | 4 | A .NET port of simplify-js (https://github.com/mourner/simplify-js)
5 | Polyline simplification library by Vladimir Agafonkin, extracted from Leaflet.
6 | For license see the original project or https://github.com/mourner/simplify-js/blob/master/LICENSE
7 |
8 | For more info on the C# .NET ported version visit my blog @ http://shz.no 9 | 10 | Targets 11 | ------- 12 | This port is written in C# as a portable library.
13 | It targets: .NET Framework 4.5, Windows 8, Windows Phone 8.1 and Windows Phone Silverlight 8 14 | 15 | Install 16 | ------- 17 | Download the precomplied library from the bin folder. See the example for more help! 18 | 19 | Performance 20 | ----------- 21 | Performance for this project has absolute been in focus. All of the operations has been tuned down to maximum performance C# can provide. 22 | 23 | Running test 24 | ------------ 25 | The tests are also ported from the original project, see own solution with the tests.
26 | I am using NUnit to setup and run the tests, nunit dll is also included.
27 | More about NUnit, go to http://nunit.org 28 | 29 | # Example # 30 | 31 | ```C# 32 | // Some points to test with 33 | var points = new[] { 34 | new Point(224.55,250.15),new Point(226.91,244.19),new Point(233.31,241.45),new Point(234.98,236.06), 35 | new Point(244.21,232.76),new Point(262.59,215.31),new Point(267.76,213.81),new Point(273.57,201.84), 36 | new Point(273.12,192.16),new Point(277.62,189.03),new Point(280.36,181.41),new Point(286.51,177.74), 37 | new Point(292.41,159.37),new Point(296.91,155.64),new Point(314.95,151.37),new Point(319.75,145.16), 38 | new Point(330.33,137.57),new Point(341.48,139.96),new Point(369.98,137.89),new Point(387.39,142.51), 39 | new Point(391.28,139.39),new Point(409.52,141.14),new Point(414.82,139.75),new Point(427.72,127.30), 40 | new Point(439.60,119.74),new Point(474.93,107.87),new Point(486.51,106.75),new Point(489.20,109.45), 41 | new Point(493.79,108.63),new Point(504.74,119.66),new Point(512.96,122.35),new Point(518.63,120.89), 42 | new Point(524.09,126.88),new Point(529.57,127.86),new Point(534.21,140.93),new Point(539.27,147.24), 43 | new Point(567.69,148.91),new Point(575.25,157.26),new Point(580.62,158.15),new Point(601.53,156.85), 44 | new Point(617.74,159.86),new Point(622.00,167.04),new Point(629.55,194.60),new Point(638.90,195.61), 45 | new Point(641.26,200.81),new Point(651.77,204.56),new Point(671.55,222.55),new Point(683.68,217.45), 46 | new Point(695.25,219.15),new Point(700.64,217.98),new Point(703.12,214.36),new Point(712.26,215.87), 47 | new Point(721.49,212.81),new Point(727.81,213.36),new Point(729.98,208.73),new Point(735.32,208.20), 48 | new Point(739.94,204.77),new Point(769.98,208.42),new Point(779.60,216.87),new Point(784.20,218.16), 49 | new Point(800.24,214.62),new Point(810.53,219.73),new Point(817.19,226.82),new Point(820.77,236.17), 50 | new Point(827.23,236.16),new Point(829.89,239.89),new Point(851.00,248.94),new Point(859.88,255.49), 51 | new Point(865.21,268.53),new Point(857.95,280.30),new Point(865.48,291.45),new Point(866.81,298.66), 52 | new Point(864.68,302.71),new Point(867.79,306.17),new Point(859.87,311.37),new Point(860.08,314.35), 53 | new Point(858.29,314.94),new Point(858.10,327.60),new Point(854.54,335.40),new Point(860.92,343.00), 54 | new Point(856.43,350.15),new Point(851.42,352.96),new Point(849.84,359.59),new Point(854.56,365.53), 55 | new Point(849.74,370.38),new Point(844.09,371.89),new Point(844.75,380.44),new Point(841.52,383.67), 56 | new Point(839.57,390.40),new Point(845.59,399.05),new Point(848.40,407.55),new Point(843.71,411.30), 57 | new Point(844.09,419.88),new Point(839.51,432.76),new Point(841.33,441.04),new Point(847.62,449.22), 58 | new Point(847.16,458.44),new Point(851.38,462.79),new Point(853.97,471.15),new Point(866.36,480.77) 59 | }; 60 | 61 | // After simplification these points should be returned 62 | var simplified = new[] { 63 | new Point(224.55,250.15),new Point(267.76,213.81),new Point(296.91,155.64),new Point(330.33,137.57), 64 | new Point(409.52,141.14),new Point(439.60,119.74),new Point(486.51,106.75),new Point(529.57,127.86), 65 | new Point(539.27,147.24),new Point(617.74,159.86),new Point(629.55,194.60),new Point(671.55,222.55), 66 | new Point(727.81,213.36),new Point(739.94,204.77),new Point(769.98,208.42),new Point(779.60,216.87), 67 | new Point(800.24,214.62),new Point(820.77,236.17),new Point(859.88,255.49),new Point(865.21,268.53), 68 | new Point(857.95,280.30),new Point(867.79,306.17),new Point(859.87,311.37),new Point(854.54,335.40), 69 | new Point(860.92,343.00),new Point(849.84,359.59),new Point(854.56,365.53),new Point(844.09,371.89), 70 | new Point(839.57,390.40),new Point(848.40,407.55),new Point(839.51,432.76),new Point(853.97,471.15), 71 | new Point(866.36,480.77)}; 72 | 73 | var utility = new SimplifyUtility(); // Or use the 3D utility: new SimplifyUtility3D(); 74 | 75 | var result = utility.Simplify(points, 5, false); 76 | 77 | Assert.AreEqual(simplified.Length, result.Count); 78 | Assert.That(simplified, Is.EquivalentTo(result)); 79 | ``` 80 | -------------------------------------------------------------------------------- /bin/Simplifynet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imshz/simplify-net/82d0c7289a17c29698562fe0687a9a4949ae2294/bin/Simplifynet.dll -------------------------------------------------------------------------------- /bin/Simplifynet.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imshz/simplify-net/82d0c7289a17c29698562fe0687a9a4949ae2294/bin/Simplifynet.pdb -------------------------------------------------------------------------------- /src/Simplifynet.CSharpPortable/ISimplifyUtility.cs: -------------------------------------------------------------------------------- 1 | // High-performance polyline simplification library 2 | // 3 | // This is a port of simplify-js by Vladimir Agafonkin, Copyright (c) 2012 4 | // https://github.com/mourner/simplify-js 5 | // 6 | // The code is ported from JavaScript to C#. 7 | // The library is created as portable and 8 | // is targeting multiple Microsoft plattforms. 9 | // 10 | // This library was ported by imshz @ http://www.shz.no 11 | // https://github.com/imshz/simplify-net 12 | // 13 | // This code is provided as is by the author. For complete license please 14 | // read the original license at https://github.com/mourner/simplify-js 15 | 16 | using System.Collections.Generic; 17 | 18 | namespace Simplifynet 19 | { 20 | public interface ISimplifyUtility 21 | { 22 | /// 23 | /// Simplifies a list of points to a shorter list of points. 24 | /// 25 | /// Points original list of points 26 | /// Tolerance tolerance in the same measurement as the point coordinates 27 | /// Enable highest quality for using Douglas-Peucker, set false for Radial-Distance algorithm 28 | /// Simplified list of points 29 | List Simplify(Point[] points, double tolerance = 0.3, bool highestQuality = false); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Simplifynet.CSharpPortable/Point.cs: -------------------------------------------------------------------------------- 1 | // High-performance polyline simplification library 2 | // 3 | // This is a port of simplify-js by Vladimir Agafonkin, Copyright (c) 2012 4 | // https://github.com/mourner/simplify-js 5 | // 6 | // The code is ported from JavaScript to C#. 7 | // The library is created as portable and 8 | // is targeting multiple Microsoft plattforms. 9 | // 10 | // This library was ported by imshz @ http://www.shz.no 11 | // https://github.com/imshz/simplify-net 12 | // 13 | // This code is provided as is by the author. For complete license please 14 | // read the original license at https://github.com/mourner/simplify-js 15 | 16 | using System; 17 | 18 | namespace Simplifynet 19 | { 20 | public class Point : IEquatable 21 | { 22 | public double X; 23 | public double Y; 24 | public double Z; 25 | 26 | public Point(double x, double y, double z = 0) 27 | { 28 | X = x; 29 | Y = y; 30 | Z = z; 31 | } 32 | 33 | public bool IsValid 34 | { 35 | get 36 | { 37 | return ((((X <= 90.0) && (Y >= -90.0)) && (Y <= 180.0)) && (X >= -180.0)); 38 | } 39 | } 40 | 41 | public override bool Equals(object obj) 42 | { 43 | if (ReferenceEquals(null, obj)) return false; 44 | if (ReferenceEquals(this, obj)) return true; 45 | if (obj.GetType() != typeof(Point) && obj.GetType() != typeof(Point)) 46 | return false; 47 | return Equals(obj as Point); 48 | } 49 | 50 | public bool Equals(Point other) 51 | { 52 | if (ReferenceEquals(null, other)) return false; 53 | if (ReferenceEquals(this, other)) return true; 54 | return other.X.Equals(X) && other.Y.Equals(Y) && other.Z.Equals(Z); 55 | } 56 | 57 | public override int GetHashCode() 58 | { 59 | unchecked 60 | { 61 | return (X.GetHashCode() * 397) ^ Y.GetHashCode() ^ Z.GetHashCode(); 62 | } 63 | } 64 | 65 | public override string ToString() 66 | { 67 | return string.Format("{0} {1} {2}", X, Y, Z); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Simplifynet.CSharpPortable/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Simplifynet.CSharpPortable")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("Simplifynet.CSharpPortable")] 14 | [assembly: AssemblyCopyright("Copyright © 2014")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: NeutralResourcesLanguage("en")] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.0.0")] 30 | [assembly: AssemblyFileVersion("1.0.0.0")] 31 | -------------------------------------------------------------------------------- /src/Simplifynet.CSharpPortable/SimplifyUtility.cs: -------------------------------------------------------------------------------- 1 | // High-performance polyline simplification library 2 | // 3 | // This is a port of simplify-js by Vladimir Agafonkin, Copyright (c) 2012 4 | // https://github.com/mourner/simplify-js 5 | // 6 | // The code is ported from JavaScript to C#. 7 | // The library is created as portable and 8 | // is targeting multiple Microsoft plattforms. 9 | // 10 | // This library was ported by imshz @ http://www.shz.no 11 | // https://github.com/imshz/simplify-net 12 | // 13 | // This code is provided as is by the author. For complete license please 14 | // read the original license at https://github.com/mourner/simplify-js 15 | 16 | using System.Collections.Generic; 17 | 18 | namespace Simplifynet 19 | { 20 | /// 21 | /// Simplification of a 2D-polyline. 22 | /// 23 | public class SimplifyUtility : ISimplifyUtility 24 | { 25 | // square distance between 2 points 26 | private double GetSquareDistance(Point p1, Point p2) 27 | { 28 | double dx = p1.X - p2.X, 29 | dy = p1.Y - p2.Y; 30 | 31 | return (dx*dx) + (dy*dy); 32 | } 33 | 34 | // square distance from a point to a segment 35 | private double GetSquareSegmentDistance(Point p, Point p1, Point p2) 36 | { 37 | var x = p1.X; 38 | var y = p1.Y; 39 | var dx = p2.X - x; 40 | var dy = p2.Y - y; 41 | 42 | if (!dx.Equals(0.0) || !dy.Equals(0.0)) 43 | { 44 | var t = ((p.X - x) * dx + (p.Y - y) * dy) / (dx * dx + dy * dy); 45 | 46 | if (t > 1) 47 | { 48 | x = p2.X; 49 | y = p2.Y; 50 | } 51 | else if (t > 0) 52 | { 53 | x += dx*t; 54 | y += dy*t; 55 | } 56 | } 57 | 58 | dx = p.X - x; 59 | dy = p.Y - y; 60 | 61 | return (dx*dx) + (dy*dy); 62 | } 63 | 64 | // rest of the code doesn't care about point format 65 | 66 | // basic distance-based simplification 67 | private List SimplifyRadialDistance(Point[] points, double sqTolerance) 68 | { 69 | var prevPoint = points[0]; 70 | var newPoints = new List {prevPoint}; 71 | Point point = null; 72 | 73 | for (var i = 1; i < points.Length; i++) 74 | { 75 | point = points[i]; 76 | 77 | if (GetSquareDistance(point, prevPoint) > sqTolerance) 78 | { 79 | newPoints.Add(point); 80 | prevPoint = point; 81 | } 82 | } 83 | 84 | if (point != null && !prevPoint.Equals(point)) 85 | newPoints.Add(point); 86 | 87 | return newPoints; 88 | } 89 | 90 | // simplification using optimized Douglas-Peucker algorithm with recursion elimination 91 | private List SimplifyDouglasPeucker(Point[] points, double sqTolerance) 92 | { 93 | var len = points.Length; 94 | var markers = new int?[len]; 95 | int? first = 0; 96 | int? last = len - 1; 97 | int? index = 0; 98 | var stack = new List(); 99 | var newPoints = new List(); 100 | 101 | markers[first.Value] = markers[last.Value] = 1; 102 | 103 | while (last != null) 104 | { 105 | var maxSqDist = 0.0d; 106 | 107 | for (int? i = first + 1; i < last; i++) 108 | { 109 | var sqDist = GetSquareSegmentDistance(points[i.Value], points[first.Value], points[last.Value]); 110 | 111 | if (sqDist > maxSqDist) 112 | { 113 | index = i; 114 | maxSqDist = sqDist; 115 | } 116 | } 117 | 118 | if (maxSqDist > sqTolerance) 119 | { 120 | markers[index.Value] = 1; 121 | stack.AddRange(new[] { first, index, index, last }); 122 | } 123 | 124 | 125 | if (stack.Count > 0) 126 | { 127 | last = stack[stack.Count - 1]; 128 | stack.RemoveAt(stack.Count - 1); 129 | } 130 | else 131 | last = null; 132 | 133 | if (stack.Count > 0) 134 | { 135 | first = stack[stack.Count - 1]; 136 | stack.RemoveAt(stack.Count - 1); 137 | } 138 | else 139 | first = null; 140 | } 141 | 142 | for (var i = 0; i < len; i++) 143 | { 144 | if (markers[i] != null) 145 | newPoints.Add(points[i]); 146 | } 147 | 148 | return newPoints; 149 | } 150 | 151 | /// 152 | /// Simplifies a list of points to a shorter list of points. 153 | /// 154 | /// Points original list of points 155 | /// Tolerance tolerance in the same measurement as the point coordinates 156 | /// Enable highest quality for using Douglas-Peucker, set false for Radial-Distance algorithm 157 | /// Simplified list of points 158 | public List Simplify(Point[] points, double tolerance = 0.3, bool highestQuality = false) 159 | { 160 | if(points == null || points.Length == 0) 161 | return new List(); 162 | 163 | var sqTolerance = tolerance*tolerance; 164 | 165 | if (highestQuality) 166 | return SimplifyDouglasPeucker(points, sqTolerance); 167 | 168 | List points2 = SimplifyRadialDistance(points, sqTolerance); 169 | return SimplifyDouglasPeucker(points2.ToArray(), sqTolerance); 170 | } 171 | 172 | /// 173 | /// Simplifies a list of points to a shorter list of points. 174 | /// 175 | /// Points original list of points 176 | /// Tolerance tolerance in the same measurement as the point coordinates 177 | /// Enable highest quality for using Douglas-Peucker, set false for Radial-Distance algorithm 178 | /// Simplified list of points 179 | public static List SimplifyArray(Point[] points, double tolerance = 0.3, bool highestQuality = false) 180 | { 181 | return new SimplifyUtility().Simplify(points, tolerance, highestQuality); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Simplifynet.CSharpPortable/SimplifyUtility3D.cs: -------------------------------------------------------------------------------- 1 | // High-performance polyline simplification library 2 | // 3 | // This is a port of simplify-js by Vladimir Agafonkin, Copyright (c) 2012 4 | // https://github.com/mourner/simplify-js 5 | // 6 | // The code is ported from JavaScript to C#. 7 | // The library is created as portable and 8 | // is targeting multiple Microsoft plattforms. 9 | // 10 | // This library was ported by imshz @ http://www.shz.no 11 | // https://github.com/imshz/simplify-net 12 | // 13 | // This code is provided as is by the author. For complete license please 14 | // read the original license at https://github.com/mourner/simplify-js 15 | 16 | using System.Collections.Generic; 17 | 18 | namespace Simplifynet 19 | { 20 | /// 21 | /// Simplification of a 3D-polyline. 22 | /// Use only the 3D version if your point contains altitude information, if no altitude information is provided the 2D library gives a 20% performance gain. 23 | /// 24 | public class SimplifyUtility3D : ISimplifyUtility 25 | { 26 | // square distance between 2 points 27 | private double GetSquareDistance(Point p1, Point p2) 28 | { 29 | double dx = p1.X - p2.X, 30 | dy = p1.Y - p2.Y, 31 | dz = p1.Z - p2.Z; 32 | 33 | return (dx*dx) + (dy*dy) + (dz*dz); 34 | } 35 | 36 | // square distance from a point to a segment 37 | private double GetSquareSegmentDistance(Point p, Point p1, Point p2) 38 | { 39 | var x = p1.X; 40 | var y = p1.Y; 41 | var z = p1.Z; 42 | var dx = p2.X - x; 43 | var dy = p2.Y - y; 44 | var dz = p2.Z - z; 45 | 46 | if (!dx.Equals(0.0) || !dy.Equals(0.0) || !dz.Equals(0.0)) 47 | { 48 | var t = ((p.X - x) * dx + (p.Y - y) * dy + (p.Z - z) * dz) / (dx * dx + dy * dy + dz * dz); 49 | 50 | if (t > 1) 51 | { 52 | x = p2.X; 53 | y = p2.Y; 54 | z = p2.Z; 55 | } 56 | else if (t > 0) 57 | { 58 | x += dx*t; 59 | y += dy*t; 60 | z += dz*t; 61 | } 62 | } 63 | 64 | dx = p.X - x; 65 | dy = p.Y - y; 66 | dz = p.Z - z; 67 | 68 | return (dx*dx) + (dy*dy) + (dz * dz); 69 | } 70 | 71 | // rest of the code doesn't care about point format 72 | 73 | // basic distance-based simplification 74 | private List SimplifyRadialDistance(Point[] points, double sqTolerance) 75 | { 76 | var prevPoint = points[0]; 77 | var newPoints = new List {prevPoint}; 78 | Point point = null; 79 | 80 | for (var i = 1; i < points.Length; i++) 81 | { 82 | point = points[i]; 83 | 84 | if (GetSquareDistance(point, prevPoint) > sqTolerance) 85 | { 86 | newPoints.Add(point); 87 | prevPoint = point; 88 | } 89 | } 90 | 91 | if (point != null && !prevPoint.Equals(point)) 92 | newPoints.Add(point); 93 | 94 | return newPoints; 95 | } 96 | 97 | // simplification using optimized Douglas-Peucker algorithm with recursion elimination 98 | private List SimplifyDouglasPeucker(Point[] points, double sqTolerance) 99 | { 100 | var len = points.Length; 101 | var markers = new int?[len]; 102 | int? first = 0; 103 | int? last = len - 1; 104 | int? index = 0; 105 | var stack = new List(); 106 | var newPoints = new List(); 107 | 108 | markers[first.Value] = markers[last.Value] = 1; 109 | 110 | while (last != null) 111 | { 112 | var maxSqDist = 0.0d; 113 | 114 | for (int? i = first + 1; i < last; i++) 115 | { 116 | var sqDist = GetSquareSegmentDistance(points[i.Value], points[first.Value], points[last.Value]); 117 | 118 | if (sqDist > maxSqDist) 119 | { 120 | index = i; 121 | maxSqDist = sqDist; 122 | } 123 | } 124 | 125 | if (maxSqDist > sqTolerance) 126 | { 127 | markers[index.Value] = 1; 128 | stack.AddRange(new[] { first, index, index, last }); 129 | } 130 | 131 | 132 | if (stack.Count > 0) 133 | { 134 | last = stack[stack.Count - 1]; 135 | stack.RemoveAt(stack.Count - 1); 136 | } 137 | else 138 | last = null; 139 | 140 | if (stack.Count > 0) 141 | { 142 | first = stack[stack.Count - 1]; 143 | stack.RemoveAt(stack.Count - 1); 144 | } 145 | else 146 | first = null; 147 | } 148 | 149 | for (var i = 0; i < len; i++) 150 | { 151 | if (markers[i] != null) 152 | newPoints.Add(points[i]); 153 | } 154 | 155 | return newPoints; 156 | } 157 | 158 | /// 159 | /// Simplifies a list of points to a shorter list of points. 160 | /// 161 | /// Points original list of points 162 | /// Tolerance tolerance in the same measurement as the point coordinates 163 | /// Enable highest quality for using Douglas-Peucker, set false for Radial-Distance algorithm 164 | /// Simplified list of points 165 | public List Simplify(Point[] points, double tolerance = 0.3, bool highestQuality = false) 166 | { 167 | if(points == null || points.Length == 0) 168 | return new List(); 169 | 170 | var sqTolerance = tolerance*tolerance; 171 | 172 | if (!highestQuality) 173 | { 174 | List points2 = SimplifyRadialDistance(points, sqTolerance); 175 | return SimplifyDouglasPeucker(points2.ToArray(), sqTolerance); 176 | } 177 | 178 | return SimplifyDouglasPeucker(points, sqTolerance); 179 | } 180 | 181 | /// 182 | /// Simplifies a list of points to a shorter list of points. 183 | /// 184 | /// Points original list of points 185 | /// Tolerance tolerance in the same measurement as the point coordinates 186 | /// Enable highest quality for using Douglas-Peucker, set false for Radial-Distance algorithm 187 | /// Simplified list of points 188 | public static List SimplifyArray(Point[] points, double tolerance = 0.3, bool highestQuality = false) 189 | { 190 | return new SimplifyUtility().Simplify(points, tolerance, highestQuality); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Simplifynet.CSharpPortable/Simplifynet.CSharpPortable.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 11.0 6 | Debug 7 | AnyCPU 8 | {31F65DF8-4049-490B-91DB-0265D9C28D17} 9 | Library 10 | Properties 11 | Simplifynet 12 | Simplifynet 13 | v4.5 14 | Profile259 15 | en-US 16 | 512 17 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 | -------------------------------------------------------------------------------- /src/Simplifynet.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Simplifynet.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Simplifynet.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("439c34e4-cd19-4f7a-ba64-d7dbf564182b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Simplifynet.Tests/SimplifyUtilityTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using NUnit.Framework; 4 | using SimplifyDotnet.Tests; 5 | 6 | namespace Simplifynet.Tests 7 | { 8 | [TestFixture] 9 | public class SimplifyUtilityTests 10 | { 11 | #region SimplifyTimings 12 | 13 | [Test] 14 | public void Simplify2DTimings() 15 | { 16 | SimplifyTimings(new SimplifyUtility()); 17 | } 18 | 19 | [Test] 20 | public void Simplify3DTimings() 21 | { 22 | SimplifyTimings(new SimplifyUtility3D()); 23 | } 24 | 25 | public void SimplifyTimings(ISimplifyUtility utility) 26 | { 27 | Stopwatch watch; 28 | var points = LongLine.GetPoints(); 29 | 30 | watch = Stopwatch.StartNew(); 31 | var firstRun3D = utility.Simplify(points); 32 | watch.Stop(); 33 | 34 | Console.WriteLine("First time utility warmup took: " + watch.ElapsedTicks + " ticks"); 35 | 36 | for (int i = 0; i < 10; i++) 37 | { 38 | const int times = 1000; 39 | long totalTime = 0; 40 | 41 | for (int b = 0; b < times; b++) 42 | { 43 | watch = Stopwatch.StartNew(); 44 | var run = utility.Simplify(points); 45 | watch.Stop(); 46 | totalTime += watch.ElapsedTicks; 47 | } 48 | 49 | Console.WriteLine(times + "x average time: " + (totalTime/times) + " ticks"); 50 | } 51 | } 52 | 53 | #endregion 54 | 55 | #region SimplifyWithMultiplePointsShouldSimplfyCorrectly 56 | 57 | [Test] 58 | public void SimplifyWithMultiplePointsShouldSimplfyCorrectly() 59 | { 60 | SimplifyWithMultiplePointsShouldSimplfyCorrectly(new SimplifyUtility()); 61 | } 62 | 63 | [Test] 64 | public void Simplify3DWithMultiplePointsShouldSimplfyCorrectly() 65 | { 66 | SimplifyWithMultiplePointsShouldSimplfyCorrectly(new SimplifyUtility3D()); 67 | } 68 | 69 | public void SimplifyWithMultiplePointsShouldSimplfyCorrectly(ISimplifyUtility utility) 70 | { 71 | var points = new[] { 72 | new Point(224.55,250.15),new Point(226.91,244.19),new Point(233.31,241.45),new Point(234.98,236.06), 73 | new Point(244.21,232.76),new Point(262.59,215.31),new Point(267.76,213.81),new Point(273.57,201.84), 74 | new Point(273.12,192.16),new Point(277.62,189.03),new Point(280.36,181.41),new Point(286.51,177.74), 75 | new Point(292.41,159.37),new Point(296.91,155.64),new Point(314.95,151.37),new Point(319.75,145.16), 76 | new Point(330.33,137.57),new Point(341.48,139.96),new Point(369.98,137.89),new Point(387.39,142.51), 77 | new Point(391.28,139.39),new Point(409.52,141.14),new Point(414.82,139.75),new Point(427.72,127.30), 78 | new Point(439.60,119.74),new Point(474.93,107.87),new Point(486.51,106.75),new Point(489.20,109.45), 79 | new Point(493.79,108.63),new Point(504.74,119.66),new Point(512.96,122.35),new Point(518.63,120.89), 80 | new Point(524.09,126.88),new Point(529.57,127.86),new Point(534.21,140.93),new Point(539.27,147.24), 81 | new Point(567.69,148.91),new Point(575.25,157.26),new Point(580.62,158.15),new Point(601.53,156.85), 82 | new Point(617.74,159.86),new Point(622.00,167.04),new Point(629.55,194.60),new Point(638.90,195.61), 83 | new Point(641.26,200.81),new Point(651.77,204.56),new Point(671.55,222.55),new Point(683.68,217.45), 84 | new Point(695.25,219.15),new Point(700.64,217.98),new Point(703.12,214.36),new Point(712.26,215.87), 85 | new Point(721.49,212.81),new Point(727.81,213.36),new Point(729.98,208.73),new Point(735.32,208.20), 86 | new Point(739.94,204.77),new Point(769.98,208.42),new Point(779.60,216.87),new Point(784.20,218.16), 87 | new Point(800.24,214.62),new Point(810.53,219.73),new Point(817.19,226.82),new Point(820.77,236.17), 88 | new Point(827.23,236.16),new Point(829.89,239.89),new Point(851.00,248.94),new Point(859.88,255.49), 89 | new Point(865.21,268.53),new Point(857.95,280.30),new Point(865.48,291.45),new Point(866.81,298.66), 90 | new Point(864.68,302.71),new Point(867.79,306.17),new Point(859.87,311.37),new Point(860.08,314.35), 91 | new Point(858.29,314.94),new Point(858.10,327.60),new Point(854.54,335.40),new Point(860.92,343.00), 92 | new Point(856.43,350.15),new Point(851.42,352.96),new Point(849.84,359.59),new Point(854.56,365.53), 93 | new Point(849.74,370.38),new Point(844.09,371.89),new Point(844.75,380.44),new Point(841.52,383.67), 94 | new Point(839.57,390.40),new Point(845.59,399.05),new Point(848.40,407.55),new Point(843.71,411.30), 95 | new Point(844.09,419.88),new Point(839.51,432.76),new Point(841.33,441.04),new Point(847.62,449.22), 96 | new Point(847.16,458.44),new Point(851.38,462.79),new Point(853.97,471.15),new Point(866.36,480.77) 97 | }; 98 | 99 | var simplified = new[] { 100 | new Point(224.55,250.15),new Point(267.76,213.81),new Point(296.91,155.64),new Point(330.33,137.57), 101 | new Point(409.52,141.14),new Point(439.60,119.74),new Point(486.51,106.75),new Point(529.57,127.86), 102 | new Point(539.27,147.24),new Point(617.74,159.86),new Point(629.55,194.60),new Point(671.55,222.55), 103 | new Point(727.81,213.36),new Point(739.94,204.77),new Point(769.98,208.42),new Point(779.60,216.87), 104 | new Point(800.24,214.62),new Point(820.77,236.17),new Point(859.88,255.49),new Point(865.21,268.53), 105 | new Point(857.95,280.30),new Point(867.79,306.17),new Point(859.87,311.37),new Point(854.54,335.40), 106 | new Point(860.92,343.00),new Point(849.84,359.59),new Point(854.56,365.53),new Point(844.09,371.89), 107 | new Point(839.57,390.40),new Point(848.40,407.55),new Point(839.51,432.76),new Point(853.97,471.15), 108 | new Point(866.36,480.77)}; 109 | 110 | var result = utility.Simplify(points, 5, false); 111 | 112 | Assert.AreEqual(simplified.Length, result.Count); 113 | Assert.That(simplified, Is.EquivalentTo(result)); 114 | } 115 | 116 | #endregion 117 | 118 | #region SimplifySinglePointResultSholdOnlyContainSinglePoint 119 | 120 | [Test] 121 | public void SimplifySinglePointResultSholdOnlyContainSinglePoint() 122 | { 123 | SimplifySinglePointResultSholdOnlyContainSinglePoint(new SimplifyUtility()); 124 | } 125 | 126 | [Test] 127 | public void Simplify3DSinglePointResultSholdOnlyContainSinglePoint() 128 | { 129 | SimplifySinglePointResultSholdOnlyContainSinglePoint(new SimplifyUtility3D()); 130 | } 131 | 132 | public void SimplifySinglePointResultSholdOnlyContainSinglePoint(ISimplifyUtility utility) 133 | { 134 | var Point = new Point(224.55, 250.15); 135 | var result = utility.Simplify(new[] { Point }); 136 | 137 | Assert.AreEqual(1, result.Count); 138 | Assert.AreEqual(result[0].X, Point.X); 139 | Assert.AreEqual(result[0].Y, Point.Y); 140 | } 141 | 142 | #endregion 143 | 144 | #region SimplifyWithEmptyArraySholdShouldReturnEmptyList 145 | 146 | [Test] 147 | public void SimplifyWithEmptyArraySholdShouldReturnEmptyList() 148 | { 149 | SimplifyWithEmptyArraySholdShouldReturnEmptyList(new SimplifyUtility()); 150 | } 151 | 152 | [Test] 153 | public void Simplify3DWithEmptyArraySholdShouldReturnEmptyList() 154 | { 155 | SimplifyWithEmptyArraySholdShouldReturnEmptyList(new SimplifyUtility3D()); 156 | } 157 | 158 | public void SimplifyWithEmptyArraySholdShouldReturnEmptyList(ISimplifyUtility utility) 159 | { 160 | var result = utility.Simplify(new Point[0]); 161 | 162 | Assert.AreEqual(0, result.Count); 163 | } 164 | 165 | #endregion 166 | } 167 | } -------------------------------------------------------------------------------- /src/Simplifynet.Tests/Simplifynet.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D1CD5CE2-1833-4624-8A74-12854024FED2} 8 | Library 9 | Properties 10 | Simplifynet.Tests 11 | Simplifynet.Tests 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | False 35 | extentions\nunit.framework.dll 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {31F65DF8-4049-490B-91DB-0265D9C28D17} 56 | Simplifynet.CSharpPortable 57 | 58 | 59 | 60 | 67 | -------------------------------------------------------------------------------- /src/Simplifynet.Tests/extentions/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imshz/simplify-net/82d0c7289a17c29698562fe0687a9a4949ae2294/src/Simplifynet.Tests/extentions/nunit.framework.dll -------------------------------------------------------------------------------- /src/Simplifynet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30324.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simplifynet.CSharpPortable", "Simplifynet.CSharpPortable\Simplifynet.CSharpPortable.csproj", "{31F65DF8-4049-490B-91DB-0265D9C28D17}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simplifynet.Tests", "Simplifynet.Tests\Simplifynet.Tests.csproj", "{D1CD5CE2-1833-4624-8A74-12854024FED2}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {31F65DF8-4049-490B-91DB-0265D9C28D17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {31F65DF8-4049-490B-91DB-0265D9C28D17}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {31F65DF8-4049-490B-91DB-0265D9C28D17}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {31F65DF8-4049-490B-91DB-0265D9C28D17}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D1CD5CE2-1833-4624-8A74-12854024FED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D1CD5CE2-1833-4624-8A74-12854024FED2}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D1CD5CE2-1833-4624-8A74-12854024FED2}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D1CD5CE2-1833-4624-8A74-12854024FED2}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | --------------------------------------------------------------------------------