├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── dotnet-core.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmark ├── ParsingBenchmark.cs ├── Program.cs ├── mapbox.vector.tile.benchmark.csproj └── testdata │ └── 14-8801-5371.vector.pbf ├── mapbox-vector-tile.sln ├── samples ├── GeoJSONConversion │ ├── Extensions │ │ ├── CoordinateExtensions.cs │ │ ├── VectorTileFeatureExtensions.cs │ │ └── VectorTileLayerExtensions.cs │ ├── GeoJSONConversion.csproj │ ├── Program.cs │ └── testfixtures │ │ └── 14-8801-5371.vector.pbf └── SkiaWindowsFormsSample │ ├── Form1.Designer.cs │ ├── Form1.cs │ ├── Form1.resx │ ├── Program.cs │ ├── SkiaWindowsFormsSample.csproj │ └── cadastral.pbf ├── src ├── AttributesParser.cs ├── ClassifyRings.cs ├── Coordinate.cs ├── ExtensionMethods │ └── IEnumerableExtensions.cs ├── FeatureParser.cs ├── GeometryParser.cs ├── VTPolygon.cs ├── VectorTile.cs ├── VectorTileFeature.cs ├── VectorTileLayer.cs ├── VectorTileParser.cs ├── ZigZag.cs └── mapbox.vector.tile.csproj └── tests ├── AttributesParserTests.cs ├── BenchmarkTests.cs ├── ClassifyRingsTests.cs ├── DeserializeSerializeBackTests.cs ├── GeometryParserTests.cs ├── IEnumerableExtensionTests.cs ├── LotsOfTagsTest.cs ├── PolygonTests.cs ├── SignedAreaTests.cs ├── TestData.cs ├── TileParserTests.cs ├── ZigZagTests.cs ├── mapbox.vector.tile.tests.csproj └── testdata ├── 14-8801-5371.vector.pbf ├── 16_34440_23455_raw.mvt ├── 43059.pbf ├── bag-17-67317-43082.pbf ├── bag_7_65_41.pbf ├── cadastral.pbf ├── issue3_2911.vector.pbf ├── lots-of-tags.vector.pbf ├── mapzen000.mvt ├── multi-line.pbf ├── multi-point.pbf ├── multi-polygon.pbf ├── polygon-with-inner.pbf ├── singleton-line.pbf ├── singleton-point.pbf ├── singleton-polygon.pbf └── stacked-multipolygon.pbf /.editorconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/.editorconfig -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET 8 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Setup .NET 8 17 | uses: actions/setup-dotnet@v3 18 | with: 19 | dotnet-version: 8.0.x 20 | - name: Install dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Test 25 | run: dotnet test --no-restore --verbosity normal 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | mapbox-vector-tile.v2.ncrunchsolution 2 | .idea/ 3 | .DS_Store 4 | *.iml 5 | *.userprefs 6 | 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | 10 | .vs 11 | 12 | # User-specific files 13 | *.suo 14 | *.user 15 | *.sln.docstates 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | x64/ 22 | build/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | 27 | # Roslyn cache directories 28 | *.ide/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | #NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | *_i.c 44 | *_p.c 45 | *_i.h 46 | *.ilk 47 | *.meta 48 | *.obj 49 | *.pch 50 | *.pdb 51 | *.pgc 52 | *.pgd 53 | *.rsp 54 | *.sbr 55 | *.tlb 56 | *.tli 57 | *.tlh 58 | *.tmp 59 | *.tmp_proj 60 | *.log 61 | *.vspscc 62 | *.vssscc 63 | .builds 64 | *.pidb 65 | *.svclog 66 | *.scc 67 | 68 | # Chutzpah Test files 69 | _Chutzpah* 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | *.cachefile 78 | 79 | # Visual Studio profiler 80 | *.psess 81 | *.vsp 82 | *.vspx 83 | 84 | # TFS 2012 Local Workspace 85 | $tf/ 86 | 87 | # Guidance Automation Toolkit 88 | *.gpState 89 | 90 | # ReSharper is a .NET coding add-in 91 | _ReSharper*/ 92 | *.[Rr]e[Ss]harper 93 | *.DotSettings.user 94 | 95 | # JustCode is a .NET coding addin-in 96 | .JustCode 97 | 98 | # TeamCity is a build add-in 99 | _TeamCity* 100 | 101 | # DotCover is a Code Coverage Tool 102 | *.dotCover 103 | 104 | # NCrunch 105 | _NCrunch_* 106 | .*crunch*.local.xml 107 | 108 | # MightyMoose 109 | *.mm.* 110 | AutoTest.Net/ 111 | 112 | # Web workbench (sass) 113 | .sass-cache/ 114 | 115 | # Installshield output folder 116 | [Ee]xpress/ 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish/ 130 | 131 | # Publish Web Output 132 | *.[Pp]ublish.xml 133 | *.azurePubxml 134 | ## TODO: Comment the next line if you want to checkin your 135 | ## web deploy settings but do note that will include unencrypted 136 | ## passwords 137 | #*.pubxml 138 | 139 | # NuGet Packages Directory 140 | packages/* 141 | ## TODO: If the tool you use requires repositories.config 142 | ## uncomment the next line 143 | #!packages/repositories.config 144 | 145 | # Enable "build/" folder in the NuGet Packages folder since 146 | # NuGet packages use it for MSBuild targets. 147 | # This line needs to be after the ignore of the build folder 148 | # (and the packages folder if the line above has been uncommented) 149 | !packages/build/ 150 | 151 | # Windows Azure Build Output 152 | csx/ 153 | *.build.csdef 154 | 155 | # Windows Store app package directory 156 | AppPackages/ 157 | 158 | # Others 159 | sql/ 160 | *.Cache 161 | ClientBin/ 162 | [Ss]tyle[Cc]op.* 163 | ~$* 164 | *~ 165 | *.dbmdl 166 | *.dbproj.schemaview 167 | *.pfx 168 | *.publishsettings 169 | node_modules/ 170 | 171 | # RIA/Silverlight projects 172 | Generated_Code/ 173 | 174 | # Backup & report files from converting an old project file 175 | # to a newer Visual Studio version. Backup files are not needed, 176 | # because we have git ;-) 177 | _UpgradeReport_Files/ 178 | Backup*/ 179 | UpgradeLog*.XML 180 | UpgradeLog*.htm 181 | 182 | # SQL Server files 183 | *.mdf 184 | *.ldf 185 | 186 | # Business Intelligence projects 187 | *.rdl.data 188 | *.bim.layout 189 | *.bim_*.settings 190 | 191 | # Microsoft Fakes 192 | FakesAssemblies/ 193 | 194 | # LightSwitch generated files 195 | GeneratedArtifacts/ 196 | _Pvt_Extensions/ 197 | ModelManifest.xml 198 | /.vs/mapbox-vector-tile/v15/sqlite3 199 | /mapbox-vector-tile.v3.ncrunchsolution 200 | /*.ncrunchsolution 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Bert Temme 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mapbox-vector-tile-cs 2 | 3 | [![NuGet Status](http://img.shields.io/nuget/v/mapbox-vector-tile.svg?style=flat)](https://www.nuget.org/packages/mapbox-vector-tile/) ![.NET 8](https://github.com/bertt/mapbox-vector-tile-cs/workflows/.NET%208/badge.svg) 4 | 5 | .NET Standard 2.0 library for decoding a Mapbox vector tile. 6 | 7 | ## Dependencies 8 | 9 | - protobuf-net https://github.com/protobuf-net/protobuf-net 10 | 11 | ## Installation 12 | 13 | ``` 14 | $ Install-Package mapbox-vector-tile 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```cs 20 | const string vtfile = "vectortile.pbf"; 21 | var stream = File.OpenRead(vtfile); 22 | var layerInfos = VectorTileParser.Parse(stream); 23 | ``` 24 | 25 | Tip: If you use this library with vector tiles loading from a webserver, you could run into the following exception: 26 | 'ProtoBuf.ProtoException: Invalid wire-type; this usually means you have over-written a file without truncating or setting the length' 27 | Probably you need to check the GZip compression, see also TileParserTests.cs for an example. 28 | 29 | ## Building 30 | 31 | ``` 32 | $ git clone https://github.com/bertt/mapbox-vector-tile-cs.git 33 | $ cd mapbox-vector-tile-cs 34 | $ dotnet build 35 | ``` 36 | 37 | ## Testing 38 | 39 | ``` 40 | $ git clone https://github.com/bertt/mapbox-vector-tile-cs.git 41 | $ cd mapbox-vector-tile-cs/tests/mapbox.vector.tile.tests 42 | $ dotnet test 43 | Passed! 44 | Failed: 0, Passed: 38, Skipped: 0, Total: 38, Duration: 937 ms 45 | ``` 46 | 47 | ## Samples 48 | 49 | 1] GeoJSON 50 | 51 | The samples folder contains a simple console application that reads a vector tile from a file and converts to GeoJSON file. 52 | 53 | 2] SkiaSharp Forms Sample 54 | 55 | SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. The samples folder contains a simple GUI application that reads a 56 | vector tile from a file and draws the geometries using SkiaSharp. 57 | 58 | ## Benchmarking 59 | 60 | Test performed with Mapbox vector tile '14-8801-5371.vector.pbf' 61 | 62 | Layers used: 63 | 64 | Point layer: parks (id=17) - 558 features 65 | 66 | Line layer: roads (id=8) - 686 features 67 | 68 | Polygon layer: building (id=5) - 975 features 69 | 70 | ``` 71 | 72 | | Method | Mean | Error | StdDev | 73 | |-------------------------- |---------:|----------:|----------:| 74 | | ParseVectorTileFromStream | 1.401 us | 0.0133 us | 0.0125 us | 75 | ``` 76 | 77 | ## History 78 | 79 | 2025-04-16: Release 5.2.3, updated licence in Nuget package 80 | 81 | 2025-02-25: Release 5.2.2, nullable enabled 82 | 83 | 2025-02-24: Release 5.2.1, improved memory handling 84 | 85 | 2025-02-24: Release 5.2, library is now .NET Standard 2.0 86 | 87 | 2025-01-29: Release 5.1, upgrading dependencies + remove GeoJSON.NET dependency + adding samples (SkiaSharp, GeoJSON) 88 | 89 | 2023-11-26: Release 5.0.2, containing .NET 8 90 | 91 | 2022-10-29: Release 5.0.1, upgrading dependencies 92 | 93 | 2022-10-29: Release 5.0 containing .NET 6 94 | 95 | 2018-08-28: Release 4.2 containing .NET Standard 2.0 96 | 97 | 2018-03-08: Release 4.1 with fix for issue 16 (https://github.com/bertt/mapbox-vector-tile-cs/issues/16 - about serializing attributes) 98 | 99 | 2017-10-19: Release 4.0 for .NET Standard 1.3 100 | 101 | 2016-11-03: Release 3.1 102 | 103 | Changes: Add support for polygon inner- and outerrings 104 | 105 | 2016-10-31: Release 3.0 106 | 107 | Changes: Add support for multi-geometries 108 | 109 | 2015-07-08: Release 2.0 110 | 111 | 2015-07-07: Release 1.0 112 | 113 | 114 | -------------------------------------------------------------------------------- /benchmark/ParsingBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Mapbox.Vector.Tile; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | 6 | namespace mapbox.vector.tile.benchmark; 7 | 8 | public class ParsingBenchmark 9 | { 10 | Stream input; 11 | public ParsingBenchmark() 12 | { 13 | const string mvtFile = @"./testdata/14-8801-5371.vector.pbf"; 14 | input = File.OpenRead(mvtFile); 15 | } 16 | 17 | [Benchmark] 18 | public List ParseVectorTileFromStream() 19 | { 20 | return VectorTileParser.Parse(input); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using System; 3 | 4 | namespace mapbox.vector.tile.benchmark; 5 | 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | var summary = BenchmarkRunner.Run(); 11 | Console.ReadKey(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /benchmark/mapbox.vector.tile.benchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | false 6 | enable 7 | 8 | 9 | 10 | PreserveNewest 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /benchmark/testdata/14-8801-5371.vector.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/benchmark/testdata/14-8801-5371.vector.pbf -------------------------------------------------------------------------------- /mapbox-vector-tile.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32929.385 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mapbox.vector.tile", "src\mapbox.vector.tile.csproj", "{E1EA7DC2-6EA3-403A-A2DB-7143C9E5B704}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mapbox.vector.tile.tests", "tests\mapbox.vector.tile.tests.csproj", "{37C76CED-224F-475D-8819-93D194161BB0}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mapbox.vector.tile.benchmark", "benchmark\mapbox.vector.tile.benchmark.csproj", "{99D6786C-8627-4CDA-B047-D0ECA0F1EADF}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{6EB1FE0B-F2F1-43C4-A801-13F25E5E78F5}" 13 | ProjectSection(SolutionItems) = preProject 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DA8D94F6-E16A-480B-B25D-4002F7F432C9}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeoJSONConversion", "samples\GeoJSONConversion\GeoJSONConversion.csproj", "{3ADBC272-EDCC-4688-A17B-3840B20BDB17}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaWindowsFormsSample", "samples\SkiaWindowsFormsSample\SkiaWindowsFormsSample.csproj", "{A862BFAB-AD2D-47FC-93D7-4EF509B79E20}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {E1EA7DC2-6EA3-403A-A2DB-7143C9E5B704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {E1EA7DC2-6EA3-403A-A2DB-7143C9E5B704}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {E1EA7DC2-6EA3-403A-A2DB-7143C9E5B704}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {E1EA7DC2-6EA3-403A-A2DB-7143C9E5B704}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {37C76CED-224F-475D-8819-93D194161BB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {37C76CED-224F-475D-8819-93D194161BB0}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {37C76CED-224F-475D-8819-93D194161BB0}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {37C76CED-224F-475D-8819-93D194161BB0}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {99D6786C-8627-4CDA-B047-D0ECA0F1EADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {99D6786C-8627-4CDA-B047-D0ECA0F1EADF}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {99D6786C-8627-4CDA-B047-D0ECA0F1EADF}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {99D6786C-8627-4CDA-B047-D0ECA0F1EADF}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {3ADBC272-EDCC-4688-A17B-3840B20BDB17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {3ADBC272-EDCC-4688-A17B-3840B20BDB17}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {3ADBC272-EDCC-4688-A17B-3840B20BDB17}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {3ADBC272-EDCC-4688-A17B-3840B20BDB17}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {A862BFAB-AD2D-47FC-93D7-4EF509B79E20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {A862BFAB-AD2D-47FC-93D7-4EF509B79E20}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(NestedProjects) = preSolution 52 | {3ADBC272-EDCC-4688-A17B-3840B20BDB17} = {DA8D94F6-E16A-480B-B25D-4002F7F432C9} 53 | {A862BFAB-AD2D-47FC-93D7-4EF509B79E20} = {DA8D94F6-E16A-480B-B25D-4002F7F432C9} 54 | EndGlobalSection 55 | GlobalSection(ExtensibilityGlobals) = postSolution 56 | SolutionGuid = {EAE9C612-1C61-4448-9730-9C3F6D1CA715} 57 | EndGlobalSection 58 | EndGlobal 59 | -------------------------------------------------------------------------------- /samples/GeoJSONConversion/Extensions/CoordinateExtensions.cs: -------------------------------------------------------------------------------- 1 | using GeoJSON.Net.Geometry; 2 | 3 | namespace Mapbox.Vector.Tile; 4 | 5 | public static class CoordinateExtensions 6 | { 7 | public static Position ToPosition(this Coordinate c, int x, int y, int z, uint extent) 8 | { 9 | var size = extent * Math.Pow(2, z); 10 | var x0 = extent * x; 11 | var y0 = extent * y; 12 | 13 | var y2 = 180 - (c.Y + y0) * 360 / size; 14 | var lon = (c.X + x0) * 360 / size - 180; 15 | var lat = 360 / Math.PI * Math.Atan(Math.Exp(y2 * Math.PI / 180)) - 90; 16 | 17 | return new Position(lat, lon); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/GeoJSONConversion/Extensions/VectorTileFeatureExtensions.cs: -------------------------------------------------------------------------------- 1 | using GeoJSON.Net.Feature; 2 | using GeoJSON.Net.Geometry; 3 | 4 | namespace Mapbox.Vector.Tile; 5 | 6 | public static class VectorTileFeatureExtensions 7 | { 8 | private static List Project(IEnumerable coords, int x, int y, int z, uint extent) 9 | { 10 | return coords.Select(coord => coord.ToPosition(x, y, z, extent)).ToList(); 11 | } 12 | 13 | private static LineString CreateLineString(List pos) 14 | { 15 | return new LineString(pos); 16 | } 17 | 18 | 19 | private static IGeometryObject GetPointGeometry(List pointList) 20 | { 21 | IGeometryObject geom; 22 | if (pointList.Count == 1) 23 | { 24 | geom = new Point(pointList[0]); 25 | } 26 | else 27 | { 28 | var pnts = pointList.Select(p => new Point(p)).ToList(); 29 | geom = new MultiPoint(pnts); 30 | } 31 | return geom; 32 | } 33 | 34 | private static List GetLineStringList(List> pointList) 35 | { 36 | return pointList.Select(part => CreateLineString(part)).ToList(); 37 | } 38 | 39 | private static IGeometryObject GetLineGeometry(List> pointList) 40 | { 41 | IGeometryObject geom; 42 | 43 | if (pointList.Count == 1) 44 | { 45 | geom = new LineString(pointList[0]); 46 | } 47 | else 48 | { 49 | geom = new MultiLineString(GetLineStringList(pointList)); 50 | } 51 | return geom; 52 | } 53 | 54 | private static Polygon GetPolygon(List> lines) 55 | { 56 | var res = new List(); 57 | foreach (var innerring in lines) 58 | { 59 | var line = new LineString(innerring); 60 | if (line.IsLinearRing() && line.IsClosed()) 61 | { 62 | res.Add(line); 63 | } 64 | } 65 | var geom = new Polygon(res); 66 | return geom; 67 | } 68 | 69 | private static IGeometryObject? GetPolygonGeometry(List>> polygons) 70 | { 71 | { 72 | IGeometryObject? geom=null; 73 | 74 | if (polygons.Count == 1) 75 | { 76 | geom = GetPolygon(polygons[0]); 77 | } 78 | else if(polygons.Count>0) 79 | { 80 | var multipolys = new List(); 81 | foreach(var poly in polygons) 82 | { 83 | var pl = GetPolygon(poly); 84 | multipolys.Add(pl); 85 | } 86 | 87 | geom = new MultiPolygon(multipolys); 88 | } 89 | return geom; 90 | } 91 | } 92 | 93 | public static List ProjectPoints(IEnumerable> Geometry, int x, int y, int z, uint extent) => 94 | Geometry.Select(g => Project(g, x, y, z, extent).Single()).ToList(); 95 | 96 | public static List> ProjectLines(IEnumerable> Geometry, int x, int y, int z, uint extent) 97 | { 98 | var projectedCoords = new List(); 99 | var pointList = new List>(); 100 | foreach (var g in Geometry) 101 | { 102 | projectedCoords = Project(g, x, y, z, extent); 103 | pointList.Add(projectedCoords); 104 | } 105 | return pointList; 106 | } 107 | 108 | public static List>> ProjectPolygons(IEnumerable>> Geometry, int x, int y, int z, uint extent) 109 | { 110 | var projectedCoords = new List>(); 111 | var result = new List>>(); 112 | foreach (var g in Geometry) 113 | { 114 | projectedCoords = ProjectLines(g, x, y, z, extent); 115 | result.Add(projectedCoords); 116 | } 117 | return result; 118 | } 119 | 120 | 121 | public static Feature ToGeoJSON(this VectorTileFeature vectortileFeature, int x, int y, int z) 122 | { 123 | IGeometryObject? geom = null; 124 | 125 | switch (vectortileFeature.GeometryType) 126 | { 127 | case Tile.GeomType.Point: 128 | var projectedPoints = ProjectPoints(vectortileFeature.Geometry.Cast>(), x, y, z, vectortileFeature.Extent); 129 | geom = GetPointGeometry(projectedPoints); 130 | break; 131 | case Tile.GeomType.LineString: 132 | var projectedLines = ProjectLines(vectortileFeature.Geometry.Cast>(), x, y, z, vectortileFeature.Extent); 133 | geom = GetLineGeometry(projectedLines); 134 | break; 135 | case Tile.GeomType.Polygon: 136 | var rings = ClassifyRings.Classify(vectortileFeature.Geometry.Cast>()); 137 | var projectedPolygons = ProjectPolygons(rings, x, y, z, vectortileFeature.Extent); 138 | geom = GetPolygonGeometry(projectedPolygons); 139 | break; 140 | } 141 | 142 | var result = new Feature(geom, id: vectortileFeature.Id); 143 | 144 | // add attributes 145 | foreach (var item in vectortileFeature.Attributes) 146 | { 147 | result.Properties.Add(item.Key, item.Value); 148 | 149 | } 150 | return result; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /samples/GeoJSONConversion/Extensions/VectorTileLayerExtensions.cs: -------------------------------------------------------------------------------- 1 | using GeoJSON.Net.Feature; 2 | 3 | namespace Mapbox.Vector.Tile; 4 | 5 | public static class VectorTileLayerExtensions 6 | { 7 | public static FeatureCollection ToGeoJSON(this VectorTileLayer vectortileLayer, int x, int y, int z) 8 | { 9 | var featureCollection = new FeatureCollection(); 10 | 11 | foreach (var feature in vectortileLayer.VectorTileFeatures) 12 | { 13 | var geojsonFeature = feature.ToGeoJSON(x,y,z); 14 | if (geojsonFeature.Geometry != null) 15 | { 16 | featureCollection.Features.Add(geojsonFeature); 17 | } 18 | } 19 | return featureCollection; 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /samples/GeoJSONConversion/GeoJSONConversion.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/GeoJSONConversion/Program.cs: -------------------------------------------------------------------------------- 1 | using Mapbox.Vector.Tile; 2 | using Newtonsoft.Json; 3 | 4 | // sample converting a vector tile to GeoJSON 5 | Console.WriteLine("Converting a vector tile to GeoJSON"); 6 | string mvtFile = @"./testfixtures/14-8801-5371.vector.pbf"; 7 | var input = File.OpenRead(mvtFile); 8 | var layers = VectorTileParser.Parse(input); 9 | var geoJson = layers[5].ToGeoJSON(8801, 5371, 14); 10 | var json = JsonConvert.SerializeObject(geoJson); 11 | File.WriteAllText("output.geojson", json); 12 | Console.WriteLine("GeoJSON written to output.geojson"); 13 | -------------------------------------------------------------------------------- /samples/GeoJSONConversion/testfixtures/14-8801-5371.vector.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/samples/GeoJSONConversion/testfixtures/14-8801-5371.vector.pbf -------------------------------------------------------------------------------- /samples/SkiaWindowsFormsSample/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WinFormsApp1 2 | { 3 | partial class Form1 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | SuspendLayout(); 32 | // 33 | // Form1 34 | // 35 | AutoScaleDimensions = new SizeF(8F, 20F); 36 | AutoScaleMode = AutoScaleMode.Font; 37 | ClientSize = new Size(800, 450); 38 | Name = "Form1"; 39 | Text = "Form1"; 40 | Load += Form1_Load; 41 | ResumeLayout(false); 42 | } 43 | 44 | #endregion 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/SkiaWindowsFormsSample/Form1.cs: -------------------------------------------------------------------------------- 1 | using Mapbox.Vector.Tile; 2 | using SkiaSharp; 3 | using SkiaSharp.Views.Desktop; 4 | 5 | namespace WinFormsApp1 6 | { 7 | public partial class Form1 : Form 8 | { 9 | private SKControl skControl; 10 | public Form1() 11 | { 12 | InitializeComponent(); 13 | skControl = new SKControl 14 | { 15 | Dock = DockStyle.Fill // Laat het de hele Form vullen 16 | }; 17 | skControl.PaintSurface += OnPaintSurface; 18 | Controls.Add(skControl); 19 | } 20 | 21 | private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) 22 | { 23 | // Verkrijg het canvas 24 | SKCanvas canvas = e.Surface.Canvas; 25 | 26 | // Wis het canvas met een achtergrondkleur 27 | canvas.Clear(SKColors.White); 28 | 29 | // Maak een pen (SKPaint) aan 30 | var paint = new SKPaint 31 | { 32 | Color = SKColors.Blue, 33 | StrokeWidth = 5, 34 | IsAntialias = true 35 | }; 36 | 37 | const string vtfile = @"cadastral.pbf"; 38 | var stream = File.OpenRead(vtfile); 39 | var layerInfos = VectorTileParser.Parse(stream); 40 | var laerInfo = layerInfos[0]; 41 | foreach (var feature in laerInfo.VectorTileFeatures) 42 | { 43 | var coords = feature.Geometry[0]; 44 | for (var i = 1; i < coords.Count; i++) 45 | { 46 | var c0 = coords[i-1]; 47 | var c1 = coords[i]; 48 | canvas.DrawLine(c0.X, c0.Y, c1.X, c1.Y, paint); 49 | } 50 | 51 | } 52 | } 53 | 54 | private void Form1_Load(object sender, EventArgs e) 55 | { 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /samples/SkiaWindowsFormsSample/Form1.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /samples/SkiaWindowsFormsSample/Program.cs: -------------------------------------------------------------------------------- 1 | namespace WinFormsApp1 2 | { 3 | internal static class Program 4 | { 5 | /// 6 | /// The main entry point for the application. 7 | /// 8 | [STAThread] 9 | static void Main() 10 | { 11 | // To customize application configuration such as set high DPI settings or default font, 12 | // see https://aka.ms/applicationconfiguration. 13 | ApplicationConfiguration.Initialize(); 14 | Application.Run(new Form1()); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /samples/SkiaWindowsFormsSample/SkiaWindowsFormsSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /samples/SkiaWindowsFormsSample/cadastral.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/samples/SkiaWindowsFormsSample/cadastral.pbf -------------------------------------------------------------------------------- /src/AttributesParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Mapbox.Vector.Tile; 6 | 7 | public static class AttributesParser 8 | { 9 | public static List> Parse(List keys, List values, List tags) 10 | { 11 | var result = new List>(); 12 | var odds = tags.GetOdds().ToList(); 13 | var evens = tags.GetEvens().ToList(); 14 | 15 | for (var i = 0; i < evens.Count; i++) 16 | { 17 | var key = keys[(int)evens[i]]; 18 | var val = values[(int)odds[i]]; 19 | var valObject = GetAttr(val); 20 | result.Add(new KeyValuePair(key, valObject)); 21 | } 22 | return result; 23 | } 24 | 25 | private static object GetAttr(Tile.Value value) 26 | { 27 | if (value.HasBoolValue) 28 | { 29 | return value.BoolValue; 30 | } 31 | else if (value.HasDoubleValue) 32 | { 33 | return value.DoubleValue; 34 | } 35 | else if (value.HasFloatValue) 36 | { 37 | return value.FloatValue; 38 | } 39 | else if (value.HasIntValue) 40 | { 41 | return value.IntValue; 42 | } 43 | else if (value.HasStringValue) 44 | { 45 | return value.StringValue; 46 | } 47 | else if (value.HasSIntValue) 48 | { 49 | return value.SintValue; 50 | } 51 | else if (value.HasUIntValue) 52 | { 53 | return value.UintValue; 54 | } 55 | else 56 | { 57 | throw new NotImplementedException("Unknown attribute type"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ClassifyRings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Mapbox.Vector.Tile; 4 | 5 | public class ClassifyRings 6 | { 7 | // docs for inner/outer rings https://www.mapbox.com/vector-tiles/specification/ 8 | public static List>> Classify(IEnumerable> rings) 9 | { 10 | var polygons = new List>>(); 11 | List>? newpoly = null; 12 | foreach (var ring in rings) 13 | { 14 | var poly = new VTPolygon(ring); 15 | 16 | if (poly.IsOuterRing()) 17 | { 18 | newpoly = [ring]; 19 | polygons.Add(newpoly); 20 | } 21 | else 22 | { 23 | newpoly?.Add(ring); 24 | } 25 | } 26 | 27 | return polygons; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Coordinate.cs: -------------------------------------------------------------------------------- 1 | namespace Mapbox.Vector.Tile; 2 | 3 | public struct Coordinate(long x, long y) 4 | { 5 | public long X { get; set; } = x; 6 | public long Y { get; set; } = y; 7 | } 8 | -------------------------------------------------------------------------------- /src/ExtensionMethods/IEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Mapbox.Vector.Tile; 5 | 6 | public static class EnumerableExtensions 7 | { 8 | public static IEnumerable GetOdds(this IEnumerable sequence) 9 | { 10 | return sequence.Where((item, index) => index % 2 != 0); 11 | } 12 | 13 | public static IEnumerable GetEvens(this IEnumerable sequence) 14 | { 15 | return sequence.Where((item, index) => index % 2 == 0); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/FeatureParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | 4 | namespace Mapbox.Vector.Tile; 5 | 6 | public static class FeatureParser 7 | { 8 | public static VectorTileFeature Parse(Tile.Feature feature, List keys, List values, uint extent) => new( 9 | id: feature.Id.ToString(CultureInfo.InvariantCulture), 10 | geometry: GeometryParser.ParseGeometry(feature.Geometry, feature.Type), 11 | attributes: AttributesParser.Parse(keys, values, feature.Tags), 12 | geometryType: feature.Type, 13 | extent: extent 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/GeometryParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Mapbox.Vector.Tile; 5 | 6 | public static class GeometryParser 7 | { 8 | private enum Command 9 | { 10 | None = 0, 11 | MoveTo = 1, 12 | LineTo = 2, 13 | Bits = 3, 14 | SegEnd = 7, 15 | } 16 | 17 | public static List> ParseGeometry(IReadOnlyList geom, Tile.GeomType geomType) 18 | { 19 | long x = 0; 20 | long y = 0; 21 | 22 | var coords = new Coordinate[geom.Count]; 23 | var insert = 0; 24 | var segmentStart = 0; 25 | 26 | uint length = 0; 27 | var command = Command.None; 28 | var i = 0; 29 | 30 | var result = new List>(); 31 | 32 | while (i < coords.Length) 33 | { 34 | if (length <= 0) 35 | { 36 | length = geom[i++]; 37 | command = (Command) (length & ((1 << 3) - 1)); 38 | length >>= 3; 39 | } 40 | 41 | if (length > 0) 42 | { 43 | if (command == Command.MoveTo && insert > segmentStart) 44 | { 45 | result.Add(new(coords, segmentStart, insert - segmentStart)); 46 | segmentStart = insert; 47 | } 48 | } 49 | 50 | if (command == Command.SegEnd) 51 | { 52 | if (geomType != Tile.GeomType.Point && insert > segmentStart) 53 | { 54 | coords[insert++] = coords[segmentStart]; 55 | } 56 | length--; 57 | continue; 58 | } 59 | 60 | var dx = geom[i++]; 61 | var dy = geom[i++]; 62 | 63 | length--; 64 | 65 | var ldx = ZigZag.Decode(dx); 66 | var ldy = ZigZag.Decode(dy); 67 | 68 | x += ldx; 69 | y += ldy; 70 | 71 | // use scale? var coord = new Coordinate(x / scale, y / scale); 72 | var coord = new Coordinate(x, y); 73 | coords[insert++] = coord; 74 | } 75 | 76 | if (insert > segmentStart) 77 | { 78 | result.Add(new(coords, segmentStart, insert - segmentStart)); 79 | } 80 | 81 | return result; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/VTPolygon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Mapbox.Vector.Tile; 4 | 5 | public class VTPolygon 6 | { 7 | private IList points; 8 | 9 | public VTPolygon(IList points) 10 | { 11 | this.points = points; 12 | } 13 | 14 | public double SignedArea() 15 | { 16 | double sum = 0.0; 17 | for (int i = 0; i < points.Count; ++i) 18 | { 19 | sum += points[i].X * (((i + 1) == points.Count) ? points[0].Y : points[i + 1].Y); 20 | sum -= points[i].Y * (((i + 1) == points.Count) ? points[0].X : points[i + 1].X); 21 | } 22 | return 0.5 * sum; 23 | } 24 | 25 | public bool IsOuterRing() 26 | { 27 | return IsCCW(); 28 | } 29 | 30 | public bool IsCW() 31 | { 32 | return SignedArea() < 0; 33 | } 34 | 35 | public bool IsCCW() 36 | { 37 | return SignedArea() > 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/VectorTile.cs: -------------------------------------------------------------------------------- 1 | namespace Mapbox.Vector.Tile; 2 | 3 | [ProtoBuf.ProtoContract(Name = @"tile")] 4 | public sealed class Tile : ProtoBuf.IExtensible 5 | { 6 | readonly System.Collections.Generic.List _layers = new System.Collections.Generic.List(); 7 | [ProtoBuf.ProtoMember(3, Name = @"layers", DataFormat = ProtoBuf.DataFormat.Default)] 8 | public System.Collections.Generic.List Layers 9 | { 10 | get { return _layers; } 11 | } 12 | 13 | [ProtoBuf.ProtoContract(Name = @"value")] 14 | public sealed class Value : ProtoBuf.IExtensible 15 | { 16 | string _stringValue = ""; 17 | 18 | public bool HasStringValue { get; set; } 19 | public bool HasFloatValue { get; set; } 20 | public bool HasDoubleValue { get; set; } 21 | public bool HasIntValue { get; set; } 22 | public bool HasUIntValue { get; set; } 23 | public bool HasSIntValue { get; set; } 24 | public bool HasBoolValue { get; set; } 25 | 26 | public bool ShouldSerializeStringValue() => HasStringValue; 27 | public bool ShouldSerializeFloatValue() => HasFloatValue; 28 | public bool ShouldSerializeDoubleValue() => HasDoubleValue; 29 | public bool ShouldSerializeIntValue() => HasIntValue; 30 | public bool ShouldSerializeUIntValue() => HasUIntValue; 31 | public bool ShouldSerializeSIntValue() => HasSIntValue; 32 | public bool ShouldSerializeBoolValue() => HasBoolValue; 33 | 34 | [ProtoBuf.ProtoMember(1, IsRequired = false, Name = @"string_value", DataFormat = ProtoBuf.DataFormat.Default)] 35 | [System.ComponentModel.DefaultValue("")] 36 | public string StringValue 37 | { 38 | get { return _stringValue; } 39 | set 40 | { 41 | HasStringValue = true; 42 | _stringValue = value; 43 | } 44 | } 45 | 46 | float _floatValue; 47 | [ProtoBuf.ProtoMember(2, IsRequired = false, Name = @"float_value", DataFormat = ProtoBuf.DataFormat.FixedSize)] 48 | [System.ComponentModel.DefaultValue(default(float))] 49 | public float FloatValue 50 | { 51 | get 52 | { 53 | return _floatValue; 54 | } 55 | set 56 | { 57 | _floatValue = value; 58 | HasFloatValue = true; 59 | 60 | } 61 | } 62 | double _doubleValue; 63 | [ProtoBuf.ProtoMember(3, IsRequired = false, Name = @"double_value", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 64 | [System.ComponentModel.DefaultValue(default(double))] 65 | public double DoubleValue 66 | { 67 | get { return _doubleValue; } 68 | set 69 | { 70 | _doubleValue = value; 71 | HasDoubleValue = true; 72 | } 73 | } 74 | long _intValue; 75 | [ProtoBuf.ProtoMember(4, IsRequired = false, Name = @"int_value", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 76 | [System.ComponentModel.DefaultValue(default(long))] 77 | public long IntValue 78 | { 79 | get { return _intValue; } 80 | set 81 | { 82 | _intValue = value; 83 | HasIntValue = true; 84 | } 85 | } 86 | ulong _uintValue; 87 | [ProtoBuf.ProtoMember(5, IsRequired = false, Name = @"uint_value", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 88 | [System.ComponentModel.DefaultValue(default(ulong))] 89 | public ulong UintValue 90 | { 91 | get { return _uintValue; } 92 | set 93 | { 94 | _uintValue = value; 95 | HasUIntValue = true; 96 | } 97 | } 98 | long _sintValue; 99 | [ProtoBuf.ProtoMember(6, IsRequired = false, Name = @"sint_value", DataFormat = ProtoBuf.DataFormat.ZigZag)] 100 | [System.ComponentModel.DefaultValue(default(long))] 101 | public long SintValue 102 | { 103 | get { return _sintValue; } 104 | set 105 | { 106 | _sintValue = value; 107 | HasSIntValue = true; 108 | } 109 | } 110 | bool _boolValue; 111 | [ProtoBuf.ProtoMember(7, IsRequired = false, Name = @"bool_value", DataFormat = ProtoBuf.DataFormat.Default)] 112 | [System.ComponentModel.DefaultValue(default(bool))] 113 | public bool BoolValue 114 | { 115 | get { return _boolValue; } 116 | set 117 | { 118 | _boolValue = value; 119 | HasBoolValue = true; 120 | } 121 | } 122 | ProtoBuf.IExtension? _extensionObject; 123 | ProtoBuf.IExtension ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 124 | { return ProtoBuf.Extensible.GetExtensionObject(ref _extensionObject, createIfMissing); } 125 | } 126 | 127 | [ProtoBuf.ProtoContract(Name = @"feature")] 128 | public sealed class Feature : ProtoBuf.IExtensible 129 | { 130 | ulong _id; 131 | [ProtoBuf.ProtoMember(1, IsRequired = false, Name = @"id", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 132 | [System.ComponentModel.DefaultValue(default(ulong))] 133 | public ulong Id 134 | { 135 | get { return _id; } 136 | set { _id = value; } 137 | } 138 | readonly System.Collections.Generic.List _tags = new System.Collections.Generic.List(); 139 | [ProtoBuf.ProtoMember(2, Name = @"tags", DataFormat = ProtoBuf.DataFormat.TwosComplement, Options = ProtoBuf.MemberSerializationOptions.Packed)] 140 | public System.Collections.Generic.List Tags 141 | { 142 | get { return _tags; } 143 | } 144 | 145 | GeomType _type = GeomType.Unknown; 146 | [ProtoBuf.ProtoMember(3, IsRequired = false, Name = @"type", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 147 | [System.ComponentModel.DefaultValue(GeomType.Unknown)] 148 | public GeomType Type 149 | { 150 | get { return _type; } 151 | set { _type = value; } 152 | } 153 | readonly System.Collections.Generic.List _geometry = new System.Collections.Generic.List(); 154 | [ProtoBuf.ProtoMember(4, Name = @"geometry", DataFormat = ProtoBuf.DataFormat.TwosComplement, Options = ProtoBuf.MemberSerializationOptions.Packed)] 155 | public System.Collections.Generic.List Geometry 156 | { 157 | get { return _geometry; } 158 | } 159 | 160 | ProtoBuf.IExtension? _extensionObject; 161 | ProtoBuf.IExtension ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 162 | { return ProtoBuf.Extensible.GetExtensionObject(ref _extensionObject, createIfMissing); } 163 | } 164 | 165 | [ProtoBuf.ProtoContract(Name = @"layer")] 166 | public sealed class Layer : ProtoBuf.IExtensible 167 | { 168 | uint _version; 169 | [ProtoBuf.ProtoMember(15, IsRequired = true, Name = @"version", DataFormat = ProtoBuf.DataFormat.Default)] 170 | public uint Version 171 | { 172 | get { return _version; } 173 | set { _version = value; } 174 | } 175 | string? _name; 176 | [ProtoBuf.ProtoMember(1, IsRequired = true, Name = @"name", DataFormat = ProtoBuf.DataFormat.Default)] 177 | public string? Name 178 | { 179 | get { return _name; } 180 | set { _name = value; } 181 | } 182 | readonly System.Collections.Generic.List _features = new System.Collections.Generic.List(); 183 | [ProtoBuf.ProtoMember(2, Name = @"features", DataFormat = ProtoBuf.DataFormat.Default)] 184 | public System.Collections.Generic.List Features 185 | { 186 | get { return _features; } 187 | } 188 | 189 | readonly System.Collections.Generic.List _keys = new System.Collections.Generic.List(); 190 | [ProtoBuf.ProtoMember(3, Name = @"keys", DataFormat = ProtoBuf.DataFormat.Default)] 191 | public System.Collections.Generic.List Keys 192 | { 193 | get { return _keys; } 194 | } 195 | 196 | readonly System.Collections.Generic.List _values = new System.Collections.Generic.List(); 197 | [ProtoBuf.ProtoMember(4, Name = @"values", DataFormat = ProtoBuf.DataFormat.Default)] 198 | public System.Collections.Generic.List Values 199 | { 200 | get { return _values; } 201 | } 202 | 203 | uint _extent = 4096; 204 | [ProtoBuf.ProtoMember(5, IsRequired = false, Name = @"extent", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 205 | [System.ComponentModel.DefaultValue((uint)4096)] 206 | public uint Extent 207 | { 208 | get { return _extent; } 209 | set { _extent = value; } 210 | } 211 | ProtoBuf.IExtension? _extensionObject; 212 | ProtoBuf.IExtension ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 213 | { return ProtoBuf.Extensible.GetExtensionObject(ref _extensionObject, createIfMissing); } 214 | } 215 | 216 | [ProtoBuf.ProtoContract(Name = @"GeomType")] 217 | public enum GeomType 218 | { 219 | 220 | [ProtoBuf.ProtoEnum(Name = @"Unknown")] 221 | Unknown = 0, 222 | 223 | [ProtoBuf.ProtoEnum(Name = @"Point")] 224 | Point = 1, 225 | 226 | [ProtoBuf.ProtoEnum(Name = @"LineString")] 227 | LineString = 2, 228 | 229 | [ProtoBuf.ProtoEnum(Name = @"Polygon")] 230 | Polygon = 3 231 | } 232 | 233 | ProtoBuf.IExtension? _extensionObject; 234 | ProtoBuf.IExtension ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 235 | { return ProtoBuf.Extensible.GetExtensionObject(ref _extensionObject, createIfMissing); } 236 | } -------------------------------------------------------------------------------- /src/VectorTileFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Mapbox.Vector.Tile; 5 | 6 | public class VectorTileFeature(string id, List> geometry, List> attributes, Tile.GeomType geometryType, uint extent) 7 | { 8 | public string Id { get; set; } = id; 9 | public List> Geometry { get; set; } = geometry; 10 | public List> Attributes { get; set; } = attributes; 11 | public Tile.GeomType GeometryType { get; set; } = geometryType; 12 | public uint Extent { get; set; } = extent; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/VectorTileLayer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Mapbox.Vector.Tile; 4 | 5 | public class VectorTileLayer(string name, uint version, uint extent) 6 | { 7 | public List VectorTileFeatures { get; set; } = []; 8 | public string Name { get; set; } = name; 9 | public uint Version { get; set; } = version; 10 | public uint Extent { get; set; } = extent; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/VectorTileParser.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Mapbox.Vector.Tile; 6 | 7 | public static class VectorTileParser 8 | { 9 | public static List Parse(Stream stream) 10 | { 11 | var tile = Serializer.Deserialize(stream); 12 | var list = new List(); 13 | foreach (var layer in tile.Layers) 14 | { 15 | var extent = layer.Extent; 16 | var vectorTileLayer = new VectorTileLayer(layer.Name ?? "[Unnamed]", layer.Version, extent); 17 | 18 | foreach (var feature in layer.Features) 19 | { 20 | var vectorTileFeature = FeatureParser.Parse(feature, layer.Keys, layer.Values, extent); 21 | vectorTileLayer.VectorTileFeatures.Add(vectorTileFeature); 22 | } 23 | list.Add(vectorTileLayer); 24 | } 25 | return list; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ZigZag.cs: -------------------------------------------------------------------------------- 1 | namespace Mapbox.Vector.Tile; 2 | 3 | public static class ZigZag 4 | { 5 | public static long Decode(long n) 6 | { 7 | return (n >> 1) ^ (-(n & 1)); 8 | } 9 | 10 | public static long Encode(long n) 11 | { 12 | return (n << 1) ^ (n >> 31); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/mapbox.vector.tile.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 12 6 | enable 7 | 5.2.3 8 | 5.2.3.0 9 | 5.2.3.0 10 | true 11 | mapbox-vector-tile 12 | Bert Temme 13 | MIT 14 | 15 | 16 | 17 | .NET library for encoding/decoding Mapbox Vector tiles 18 | https://github.com/bertt/mapbox-vector-tile-cs 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/AttributesParserTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using NUnit.Framework; 3 | using ProtoBuf; 4 | 5 | namespace Mapbox.Vector.Tile.tests; 6 | 7 | public class AttributesParserTests 8 | { 9 | [Test] 10 | public void TestAttributeParser() 11 | { 12 | // arrange 13 | const string mapboxfile = "14-8801-5371.vector.pbf"; 14 | var pbfStream = File.OpenRead(Path.Combine("testdata", mapboxfile)); 15 | var tile = Serializer.Deserialize(pbfStream); 16 | var keys = tile.Layers[0].Keys; 17 | var values = tile.Layers[0].Values; 18 | var tagsf1 = tile.Layers[0].Features[0].Tags; 19 | 20 | // act 21 | var attributes = AttributesParser.Parse(keys, values, tagsf1); 22 | 23 | // assert 24 | Assert.That(attributes.Count == 2); 25 | Assert.That(attributes[0].Key == "class"); 26 | Assert.That((string)attributes[0].Value == "park"); 27 | Assert.That(attributes[1].Key == "osm_id"); 28 | Assert.That(attributes[1].Value.ToString() == "3000000224480"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/BenchmarkTests.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Mapbox.Vector.Tile; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | namespace mapbox.vector.tile.tests 7 | { 8 | public class BenchmarkTests 9 | { 10 | [Benchmark] 11 | public List DoParse() 12 | { 13 | // arrange 14 | const string mapboxissue3File = "mapbox.vector.tile.benchmark.testdata.mapbox_tile.vector.pbf"; 15 | 16 | // act 17 | var pbfStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(mapboxissue3File); 18 | 19 | return VectorTileParser.Parse(pbfStream); 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/ClassifyRingsTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | 4 | namespace Mapbox.Vector.Tile.tests; 5 | 6 | public class ClassifyRingsTests 7 | { 8 | [Test] 9 | public void TestReversePolygon() 10 | { 11 | var poly1 = TestData.GetCWPolygon(2); 12 | Assert.That(new VTPolygon(poly1).IsCW()); 13 | poly1.Reverse(); 14 | Assert.That(new VTPolygon(poly1).IsCCW()); 15 | } 16 | 17 | [Test] 18 | public void TestClassifyRingsWithTwoOuterrings() 19 | { 20 | // arrange 21 | var coords = new List>(); 22 | var poly1 = TestData.GetCCWPolygon(2); 23 | var poly2 = TestData.GetCCWPolygon(1); 24 | coords.Add(poly1); 25 | coords.Add(poly2); 26 | 27 | // act 28 | var classify = ClassifyRings.Classify(coords); 29 | 30 | // assert 31 | Assert.That(classify.Count == 2); 32 | Assert.That(classify[0].Count == 1); 33 | Assert.That(classify[1].Count == 1); 34 | } 35 | 36 | [Test] 37 | public void TestClassifyRingsWithOuterAndInnerRing() 38 | { 39 | // arrange 40 | var coords = new List>(); 41 | var poly1 = TestData.GetCCWPolygon(2); 42 | var poly2 = TestData.GetCWPolygon(1); 43 | coords.Add(poly1); 44 | coords.Add(poly2); 45 | 46 | // act 47 | var classify = ClassifyRings.Classify(coords); 48 | 49 | // assert 50 | Assert.That(classify.Count == 1); 51 | Assert.That(classify[0].Count == 2); 52 | } 53 | 54 | [Test] 55 | public void TestClassifyRingsWithOuterAndInnerRingAndOuterring() 56 | { 57 | // arrange 58 | var coords = new List>(); 59 | var poly1 = TestData.GetCCWPolygon(3); 60 | var poly2 = TestData.GetCWPolygon(2); 61 | var poly3 = TestData.GetCCWPolygon(1); 62 | 63 | coords.Add(poly1); 64 | coords.Add(poly2); 65 | coords.Add(poly3); 66 | 67 | // act 68 | var classify = ClassifyRings.Classify(coords); 69 | 70 | // assert 71 | Assert.That(classify.Count == 2); 72 | Assert.That(classify[0].Count == 2); 73 | Assert.That(classify[1].Count == 1); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/DeserializeSerializeBackTests.cs: -------------------------------------------------------------------------------- 1 | using Mapbox.Vector.Tile; 2 | using NUnit.Framework; 3 | using ProtoBuf; 4 | using System.IO; 5 | 6 | namespace mapbox.vector.tile.tests; 7 | 8 | public class DeserializeSerializeBackTests 9 | { 10 | [Test] 11 | public void TestIssue16() 12 | { 13 | string pbf = "16_34440_23455_raw.mvt"; 14 | var pbfStream = File.OpenRead(Path.Combine("testdata", pbf)); 15 | 16 | // deserialize the tile first 17 | var tile = Serializer.Deserialize(pbfStream); 18 | Assert.That(tile.Layers[4].Name == "road___1"); 19 | Assert.That(tile.Layers[4].Values[1].IntValue == 0); 20 | Assert.That(tile.Layers[4].Values[1].HasIntValue); 21 | Assert.That(tile.Layers[4].Values[0].StringValue == ""); 22 | Assert.That(tile.Layers[4].Values[0].HasStringValue); 23 | 24 | // it is enough to serialize into stream 25 | var serializedTileStream = new MemoryStream(); 26 | Serializer.Serialize(serializedTileStream, tile); 27 | 28 | // read the stream again 29 | serializedTileStream.Seek(0, SeekOrigin.Begin); 30 | var deserializedTile = Serializer.Deserialize(serializedTileStream); 31 | 32 | Assert.That(deserializedTile.Layers[4].Name == "road___1"); 33 | Assert.That(deserializedTile.Layers[4].Values[1].IntValue == 0); 34 | Assert.That(deserializedTile.Layers[4].Values[1].HasIntValue); 35 | Assert.That(deserializedTile.Layers[4].Values[0].StringValue == ""); 36 | Assert.That(deserializedTile.Layers[4].Values[0].HasStringValue); 37 | Assert.That(deserializedTile.Layers[4].Values[1].HasFloatValue, Is.False); 38 | Assert.That(deserializedTile.Layers[4].Values[1].HasStringValue, Is.False); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/GeometryParserTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Mapbox.Vector.Tile.tests; 6 | 7 | public class GeometryParserTests 8 | { 9 | [Test] 10 | public void AnotherGeometryParserTest() 11 | { 12 | var input = new List { 9, 7796, 3462 }; 13 | var output = GeometryParser.ParseGeometry(input, Tile.GeomType.Point); 14 | Assert.That(output.ToList().Count == 1); 15 | Assert.That(output.ToList()[0][0].X == 3898); 16 | Assert.That(output.ToList()[0][0].Y == 1731); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/IEnumerableExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Mapbox.Vector.Tile.tests; 6 | 7 | public class EnumerableExtensionTests 8 | { 9 | [Test] 10 | public void TestEvensMethod() 11 | { 12 | // arrange 13 | var sequence = new List { 0, 1, 2, 3 }; 14 | 15 | // act 16 | var evens = sequence.GetEvens().ToList(); 17 | 18 | // assert 19 | Assert.That(evens[0] == 0); 20 | Assert.That(evens[1] == 2); 21 | } 22 | 23 | [Test] 24 | public void TestOddsMethod() 25 | { 26 | // arrange 27 | var sequence = new List { 0, 1, 2, 3 }; 28 | 29 | // act 30 | var evens = sequence.GetOdds().ToList(); 31 | 32 | // assert 33 | Assert.That(evens[0] == 1); 34 | Assert.That(evens[1] == 3); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/LotsOfTagsTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.IO; 3 | 4 | namespace Mapbox.Vector.Tile.tests; 5 | 6 | public class LotsOfTagsTest 7 | { 8 | [Test] 9 | public void TestLotsOfTags() 10 | { 11 | // arrange 12 | const string mapboxfile = "lots-of-tags.vector.pbf"; 13 | var pbfStream = File.OpenRead(Path.Combine("testdata", mapboxfile)); 14 | 15 | // act 16 | var layerInfos = VectorTileParser.Parse(pbfStream); 17 | 18 | // assert 19 | Assert.That(layerInfos[0] != null); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/PolygonTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Mapbox.Vector.Tile.tests; 4 | 5 | public class PolygonTests 6 | { 7 | [Test] 8 | public void TestCWPolygon() 9 | { 10 | // arrange 11 | var coords = TestData.GetCWPolygon(1); 12 | var poly = new VTPolygon(coords); 13 | 14 | // act 15 | var ccw = poly.IsCW(); 16 | 17 | // assert 18 | Assert.That(poly.SignedArea() < 0); 19 | Assert.That(ccw); 20 | } 21 | 22 | [Test] 23 | public void TestCCWPolygon() 24 | { 25 | var coords = TestData.GetCCWPolygon(1); 26 | var poly = new VTPolygon(coords); 27 | 28 | // act 29 | var ccw = poly.IsCCW(); 30 | 31 | // assert 32 | Assert.That(poly.SignedArea() > 0); 33 | Assert.That(ccw); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/SignedAreaTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | 4 | namespace Mapbox.Vector.Tile.tests; 5 | 6 | public class SignedAreaTests 7 | { 8 | [Test] 9 | public void SignedAreaTest() 10 | { 11 | // arrange 12 | // create a closed polygon (first point is the same as the last) 13 | var points = new List(); 14 | points.Add(new Coordinate() { X = 1, Y = 1 }); 15 | points.Add(new Coordinate() { X = 2, Y = 2 }); 16 | points.Add(new Coordinate() { X = 3, Y = 1 }); 17 | points.Add(new Coordinate() { X = 1, Y = 1 }); 18 | 19 | var polygon = new VTPolygon(points); 20 | 21 | // act 22 | var area = polygon.SignedArea(); 23 | 24 | // assert 25 | // polygon is defined clock-wise so area should be negative 26 | Assert.That(area == -1); 27 | } 28 | 29 | [Test] 30 | public void SignedAreaTest1() 31 | { 32 | // arrange 33 | // create a closed polygon (first point is the same as the last) 34 | var points = new List(); 35 | points.Add(new Coordinate() { X = -3, Y = -2 }); 36 | points.Add(new Coordinate() { X = -1, Y = 4 }); 37 | points.Add(new Coordinate() { X = 6, Y = 1 }); 38 | points.Add(new Coordinate() { X = 3, Y = 10 }); 39 | points.Add(new Coordinate() { X = -4, Y = 9 }); 40 | points.Add(new Coordinate() { X = -3, Y = -2 }); 41 | 42 | var polygon = new VTPolygon(points); 43 | 44 | // act 45 | var area = polygon.SignedArea(); 46 | 47 | // assert 48 | // polygon is defined clock-wise so area should be negative 49 | Assert.That(area == 60); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /tests/TestData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Mapbox.Vector.Tile.tests; 4 | 5 | public class TestData 6 | { 7 | public static List GetCWPolygon(int factor) 8 | { 9 | var firstp = new Coordinate() { X = factor, Y = factor }; 10 | var secondp = new Coordinate() { X = factor, Y = -factor }; 11 | var thirdp = new Coordinate() { X = -factor, Y = -factor }; 12 | var fourthp = new Coordinate() { X = -factor, Y = factor }; 13 | var coords = new List() { firstp, secondp, thirdp, fourthp, firstp }; 14 | return coords; 15 | } 16 | 17 | public static List GetCCWPolygon(int factor) 18 | { 19 | // arrange 20 | var firstp = new Coordinate() { X = factor, Y = factor }; 21 | var secondp = new Coordinate() { X = -factor, Y = factor }; 22 | var thirdp = new Coordinate() { X = -factor, Y = -factor }; 23 | var fourthp = new Coordinate() { X = factor, Y = -factor }; 24 | var coords = new List() { firstp, secondp, thirdp, fourthp, firstp }; 25 | return coords; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/TileParserTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | using static Mapbox.Vector.Tile.Tile; 9 | 10 | namespace Mapbox.Vector.Tile.tests; 11 | 12 | public class TileParserTests 13 | { 14 | [Test] 15 | // test for issue 10 https://github.com/bertt/mapbox-vector-tile-cs/issues/10 16 | // Attributes tests for short Int values 17 | public void TestIssue10MapBoxVectorTile() 18 | { 19 | // arrange 20 | const string mapboxissue10File = "cadastral.pbf"; 21 | 22 | // act 23 | var pbfStream = File.OpenRead(Path.Combine("testdata", mapboxissue10File)); 24 | var layerInfos = VectorTileParser.Parse(pbfStream); 25 | 26 | // asserts 27 | var firstattribute = layerInfos[0].VectorTileFeatures[0].Attributes[0]; 28 | var val = firstattribute.Value; 29 | Assert.That((long)val == 867160); 30 | } 31 | 32 | [Test] 33 | // test for issue 3 https://github.com/bertt/mapbox-vector-tile-cs/issues/3 34 | // tile: https://b.tiles.mapbox.com/v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v7/13/4260/2911.vector.pbf 35 | public void TestIssue3MapBoxVectorTile() 36 | { 37 | // arrange 38 | const string mapboxissue3File = "issue3_2911.vector.pbf"; 39 | 40 | // act 41 | var pbfStream = File.OpenRead(Path.Combine("testdata", mapboxissue3File)); 42 | var layerInfos = VectorTileParser.Parse(pbfStream); 43 | 44 | // asserts 45 | Assert.That(layerInfos[7].VectorTileFeatures.Count == 225); 46 | Assert.That(layerInfos[0].Version == 2); 47 | Assert.That(layerInfos[7].Name == "road"); 48 | Assert.That(layerInfos[7].Extent == 4096); 49 | var firstroad = layerInfos[7].VectorTileFeatures[0]; 50 | Assert.That(firstroad.Geometry.Count == 5); 51 | Assert.That(firstroad.Geometry[0].Count == 1); 52 | Assert.That(firstroad.Geometry[0][0].X == 816); 53 | Assert.That(firstroad.Geometry[0][0].Y == 3446); 54 | 55 | var secondroad = layerInfos[7].VectorTileFeatures[1]; 56 | Assert.That(secondroad.Geometry.Count == 2); 57 | Assert.That(secondroad.Geometry[0].Count == 9); 58 | Assert.That(secondroad.Geometry[0][0].X == 3281); 59 | Assert.That(secondroad.Geometry[0][0].Y == 424); 60 | } 61 | 62 | [Test] 63 | public void TestBagVectorTile() 64 | { 65 | // arrange 66 | const string bagfile = "bag-17-67317-43082.pbf"; 67 | 68 | // act 69 | var pbfStream = File.OpenRead(Path.Combine("testdata", bagfile)); 70 | var layerInfos = VectorTileParser.Parse(pbfStream); 71 | 72 | // assert 73 | Assert.That(layerInfos.Count == 1); 74 | Assert.That(layerInfos[0].VectorTileFeatures.Count == 83); 75 | Assert.That(layerInfos[0].VectorTileFeatures[0].GeometryType == GeomType.Polygon); 76 | } 77 | 78 | [Test] 79 | public async Task TestTrailViewTileFromUrl() 80 | { 81 | // arrange 82 | var url = "https://trailview-tiles.maps.komoot.net/tiles/v2/9/264/167.vector.pbf"; 83 | 84 | // Note: Use HttpClient with automatic decompression 85 | // instead of regular HttpClient otherwise we get exception 86 | // 'ProtoBuf.ProtoException: Invalid wire-type; this usually means you have over-written a file without truncating or setting the length' 87 | var gzipWebClient = new HttpClient(new HttpClientHandler() 88 | { 89 | AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate 90 | }); 91 | var bytes = await gzipWebClient.GetByteArrayAsync(url); 92 | 93 | var stream = new MemoryStream(bytes); 94 | 95 | // act 96 | var layerInfos = VectorTileParser.Parse(stream); 97 | 98 | // assert 99 | Assert.That(layerInfos.Count > 0); 100 | } 101 | 102 | 103 | [Test] 104 | public void TestMapzenTile() 105 | { 106 | // arrange 107 | const string mapzenfile = "mapzen000.mvt"; 108 | 109 | // act 110 | var pbfStream = File.OpenRead(Path.Combine("testdata", mapzenfile)); 111 | var layerInfos = VectorTileParser.Parse(pbfStream); 112 | 113 | // assert 114 | Assert.That(layerInfos.Count == 10); 115 | } 116 | 117 | [Test] 118 | // tests from https://github.com/mapbox/vector-tile-js/blob/master/test/parse.test.js 119 | public void TestMapBoxVectorTileWithGeographicPositions() 120 | { 121 | } 122 | 123 | [Test] 124 | public void TestMapBoxVectorTileNew() 125 | { 126 | // arrange 127 | const string mapboxfile = "14-8801-5371.vector.pbf"; 128 | 129 | // act 130 | var pbfStream = File.OpenRead(Path.Combine("testdata", mapboxfile)); 131 | var layerInfos = VectorTileParser.Parse(pbfStream); 132 | 133 | // check features 134 | Assert.That(layerInfos.Count == 20); 135 | Assert.That(layerInfos[0].VectorTileFeatures.Count == 107); 136 | Assert.That(layerInfos[0].VectorTileFeatures[0].Attributes.Count == 2); 137 | 138 | // check park feature 139 | var park = layerInfos[17].VectorTileFeatures[11]; 140 | var firstOrDefault = (from prop in park.Attributes where prop.Key == "name" select prop.Value).FirstOrDefault(); 141 | if (firstOrDefault != null) 142 | { 143 | var namePark = firstOrDefault.ToString(); 144 | Assert.That(namePark == "Mauerpark"); 145 | } 146 | 147 | // check point geometry type from park 148 | Assert.That(park.Id == "3000003150561"); 149 | Assert.That(park.GeometryType == GeomType.Point); 150 | Assert.That(park.Geometry.Count == 1); 151 | Assert.That(park.Geometry[0].Count == 1); 152 | var p = park.Geometry[0][0]; 153 | Assert.That(Math.Abs(p.X - 3898) < 0.1); 154 | Assert.That(Math.Abs(p.Y - 1731) < 0.1); 155 | 156 | // Check line geometry from roads 157 | var road = layerInfos[8].VectorTileFeatures[656]; 158 | Assert.That(road.Id == "241452814"); 159 | Assert.That(road.GeometryType == GeomType.LineString); 160 | var ls = road.Geometry; 161 | Assert.That(ls.Count == 1); 162 | Assert.That(ls[0].Count == 3); 163 | var firstPoint = ls[0][0]; 164 | Assert.That(Math.Abs(firstPoint.X - 1988) < 0.1); 165 | Assert.That(Math.Abs(firstPoint.Y - 306) < 0.1); 166 | 167 | var secondPoint = ls[0][1]; 168 | Assert.That(Math.Abs(secondPoint.X - 1808) < 0.1); 169 | Assert.That(Math.Abs(secondPoint.Y - 321) < 0.1); 170 | 171 | var thirdPoint = ls[0][2]; 172 | Assert.That(Math.Abs(thirdPoint.X - 1506) < 0.1); 173 | Assert.That(Math.Abs(thirdPoint.Y - 347) < 0.1); 174 | 175 | // Check polygon geometry for buildings 176 | var building = layerInfos[5].VectorTileFeatures[0]; 177 | Assert.That(building.Id == "1000267229912"); 178 | Assert.That(building.GeometryType == GeomType.Polygon); 179 | var b = building.Geometry; 180 | Assert.That(b.Count == 1); 181 | Assert.That(b[0].Count == 5); 182 | firstPoint = b[0][0]; 183 | Assert.That(Math.Abs(firstPoint.X - 2039) < 0.1); 184 | Assert.That(Math.Abs(firstPoint.Y + 32) < 0.1); 185 | secondPoint = b[0][1]; 186 | Assert.That(Math.Abs(secondPoint.X - 2035) < 0.1); 187 | Assert.That(Math.Abs(secondPoint.Y + 31) < 0.1); 188 | thirdPoint = b[0][2]; 189 | Assert.That(Math.Abs(thirdPoint.X - 2032) < 0.1); 190 | Assert.That(Math.Abs(thirdPoint.Y + 31) < 0.1); 191 | var fourthPoint = b[0][3]; 192 | Assert.That(Math.Abs(fourthPoint.X - 2032) < 0.1); 193 | Assert.That(Math.Abs(fourthPoint.Y + 32) < 0.1); 194 | var fifthPoint = b[0][4]; 195 | Assert.That(Math.Abs(fifthPoint.X - 2039) < 0.1); 196 | Assert.That(Math.Abs(fifthPoint.Y + 32) < 0.1); 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /tests/ZigZagTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Mapbox.Vector.Tile.tests; 4 | 5 | public class ZigZagTests 6 | { 7 | [Test] 8 | public void TestZigZagDecode() 9 | { 10 | // arrange 11 | const int inputVar = 1; 12 | 13 | // act 14 | var res = ZigZag.Decode(inputVar); 15 | 16 | // assert 17 | Assert.That(res == -1); 18 | } 19 | 20 | [Test] 21 | public void AnotherTestZigZagDecode() 22 | { 23 | // arrange 24 | const int inputVar = 3; 25 | 26 | // act 27 | var res = ZigZag.Decode(inputVar); 28 | 29 | // assert 30 | Assert.That(res == -2); 31 | } 32 | 33 | [Test] 34 | public void TestZigZagEncode() 35 | { 36 | // arrange 37 | const int inputVar = -2; 38 | 39 | // act 40 | var res = ZigZag.Encode(inputVar); 41 | 42 | // assert 43 | Assert.That(res == 3); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/mapbox.vector.tile.tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | false 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/testdata/14-8801-5371.vector.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/14-8801-5371.vector.pbf -------------------------------------------------------------------------------- /tests/testdata/16_34440_23455_raw.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/16_34440_23455_raw.mvt -------------------------------------------------------------------------------- /tests/testdata/43059.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/43059.pbf -------------------------------------------------------------------------------- /tests/testdata/bag-17-67317-43082.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/bag-17-67317-43082.pbf -------------------------------------------------------------------------------- /tests/testdata/bag_7_65_41.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/bag_7_65_41.pbf -------------------------------------------------------------------------------- /tests/testdata/cadastral.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/cadastral.pbf -------------------------------------------------------------------------------- /tests/testdata/issue3_2911.vector.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/issue3_2911.vector.pbf -------------------------------------------------------------------------------- /tests/testdata/lots-of-tags.vector.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/lots-of-tags.vector.pbf -------------------------------------------------------------------------------- /tests/testdata/mapzen000.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/mapzen000.mvt -------------------------------------------------------------------------------- /tests/testdata/multi-line.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/multi-line.pbf -------------------------------------------------------------------------------- /tests/testdata/multi-point.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/multi-point.pbf -------------------------------------------------------------------------------- /tests/testdata/multi-polygon.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/multi-polygon.pbf -------------------------------------------------------------------------------- /tests/testdata/polygon-with-inner.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/polygon-with-inner.pbf -------------------------------------------------------------------------------- /tests/testdata/singleton-line.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/singleton-line.pbf -------------------------------------------------------------------------------- /tests/testdata/singleton-point.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/singleton-point.pbf -------------------------------------------------------------------------------- /tests/testdata/singleton-polygon.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/singleton-polygon.pbf -------------------------------------------------------------------------------- /tests/testdata/stacked-multipolygon.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/58508d6f08e9d46c472849c64d13b8f4ecc45ea0/tests/testdata/stacked-multipolygon.pbf --------------------------------------------------------------------------------