├── .editorconfig
├── tests
├── testdata
│ ├── 43059.pbf
│ ├── bag_7_65_41.pbf
│ ├── cadastral.pbf
│ ├── mapzen000.mvt
│ ├── multi-line.pbf
│ ├── multi-point.pbf
│ ├── multi-polygon.pbf
│ ├── singleton-line.pbf
│ ├── singleton-point.pbf
│ ├── 16_34440_23455_raw.mvt
│ ├── bag-17-67317-43082.pbf
│ ├── issue3_2911.vector.pbf
│ ├── polygon-with-inner.pbf
│ ├── singleton-polygon.pbf
│ ├── 14-8801-5371.vector.pbf
│ ├── lots-of-tags.vector.pbf
│ └── stacked-multipolygon.pbf
├── mapbox.vector.tile.tests.v3.ncrunchproject
├── LotsOfTagsTest.cs
├── GeometryParserTests.cs
├── BenchmarkTests.cs
├── PolygonTests.cs
├── IEnumerableExtensionTests.cs
├── ZigZagTests.cs
├── mapbox.vector.tile.tests.csproj
├── AttributesParserTests.cs
├── TestData.cs
├── SignedAreaTests.cs
├── DeserializeSerializeBackTests.cs
├── RealTileRoundTripTest.cs
├── ClassifyRingsTests.cs
├── VectorTileEncoderTests.cs
└── TileParserTests.cs
├── samples
├── AvaloniaSample
│ ├── cadastral.pbf
│ ├── AvaloniaSample.v3.ncrunchproject
│ ├── App.axaml
│ ├── MainWindow.axaml
│ ├── App.axaml.cs
│ ├── Program.cs
│ ├── app.manifest
│ ├── AvaloniaSample.csproj
│ ├── README.md
│ └── MainWindow.axaml.cs
└── GeoJSONConversion
│ ├── testfixtures
│ └── 14-8801-5371.vector.pbf
│ ├── GeoJSONConversion.v3.ncrunchproject
│ ├── Program.cs
│ ├── Extensions
│ ├── CoordinateExtensions.cs
│ ├── VectorTileLayerExtensions.cs
│ └── VectorTileFeatureExtensions.cs
│ └── GeoJSONConversion.csproj
├── benchmark
├── testdata
│ └── 14-8801-5371.vector.pbf
├── mapbox.vector.tile.benchmark.v3.ncrunchproject
├── Program.cs
├── ParsingBenchmark.cs
└── mapbox.vector.tile.benchmark.csproj
├── src
├── Coordinate.cs
├── mapbox.vector.tile.v3.ncrunchproject
├── ZigZag.cs
├── VectorTileLayer.cs
├── ExtensionMethods
│ └── IEnumerableExtensions.cs
├── FeatureParser.cs
├── VectorTileFeature.cs
├── ClassifyRings.cs
├── VTPolygon.cs
├── VectorTileParser.cs
├── mapbox.vector.tile.csproj
├── AttributesParser.cs
├── VectorTileEncoder.cs
├── GeometryParser.cs
├── GeometryEncoder.cs
├── AttributesEncoder.cs
└── VectorTile.cs
├── .github
└── workflows
│ └── dotnet-core.yml
├── LICENSE
├── .gitattributes
├── mapbox-vector-tile.sln
├── .gitignore
└── README.md
/.editorconfig:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/testdata/43059.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/43059.pbf
--------------------------------------------------------------------------------
/tests/testdata/bag_7_65_41.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/bag_7_65_41.pbf
--------------------------------------------------------------------------------
/tests/testdata/cadastral.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/cadastral.pbf
--------------------------------------------------------------------------------
/tests/testdata/mapzen000.mvt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/mapzen000.mvt
--------------------------------------------------------------------------------
/tests/testdata/multi-line.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/multi-line.pbf
--------------------------------------------------------------------------------
/tests/testdata/multi-point.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/multi-point.pbf
--------------------------------------------------------------------------------
/tests/testdata/multi-polygon.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/multi-polygon.pbf
--------------------------------------------------------------------------------
/tests/testdata/singleton-line.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/singleton-line.pbf
--------------------------------------------------------------------------------
/tests/testdata/singleton-point.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/singleton-point.pbf
--------------------------------------------------------------------------------
/samples/AvaloniaSample/cadastral.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/samples/AvaloniaSample/cadastral.pbf
--------------------------------------------------------------------------------
/tests/testdata/16_34440_23455_raw.mvt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/16_34440_23455_raw.mvt
--------------------------------------------------------------------------------
/tests/testdata/bag-17-67317-43082.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/bag-17-67317-43082.pbf
--------------------------------------------------------------------------------
/tests/testdata/issue3_2911.vector.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/issue3_2911.vector.pbf
--------------------------------------------------------------------------------
/tests/testdata/polygon-with-inner.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/polygon-with-inner.pbf
--------------------------------------------------------------------------------
/tests/testdata/singleton-polygon.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/singleton-polygon.pbf
--------------------------------------------------------------------------------
/tests/testdata/14-8801-5371.vector.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/14-8801-5371.vector.pbf
--------------------------------------------------------------------------------
/tests/testdata/lots-of-tags.vector.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/lots-of-tags.vector.pbf
--------------------------------------------------------------------------------
/tests/testdata/stacked-multipolygon.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/tests/testdata/stacked-multipolygon.pbf
--------------------------------------------------------------------------------
/benchmark/testdata/14-8801-5371.vector.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/benchmark/testdata/14-8801-5371.vector.pbf
--------------------------------------------------------------------------------
/samples/GeoJSONConversion/testfixtures/14-8801-5371.vector.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertt/mapbox-vector-tile-cs/HEAD/samples/GeoJSONConversion/testfixtures/14-8801-5371.vector.pbf
--------------------------------------------------------------------------------
/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/mapbox.vector.tile.v3.ncrunchproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/mapbox.vector.tile.tests.v3.ncrunchproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/AvaloniaSample.v3.ncrunchproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/benchmark/mapbox.vector.tile.benchmark.v3.ncrunchproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/GeoJSONConversion/GeoJSONConversion.v3.ncrunchproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace VelloAvaloniaSample
6 | {
7 | public partial class App : Application
8 | {
9 | public override void Initialize()
10 | {
11 | AvaloniaXamlLoader.Load(this);
12 | }
13 |
14 | public override void OnFrameworkInitializationCompleted()
15 | {
16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
17 | {
18 | desktop.MainWindow = new MainWindow();
19 | }
20 |
21 | base.OnFrameworkInitializationCompleted();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using System;
3 |
4 | namespace VelloAvaloniaSample
5 | {
6 | internal class Program
7 | {
8 | // Initialization code. Don't use any Avalonia, third-party APIs or any
9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
10 | // yet and stuff might break.
11 | [STAThread]
12 | public static void Main(string[] args) => BuildAvaloniaApp()
13 | .StartWithClassicDesktopLifetime(args);
14 |
15 | // Avalonia configuration, don't remove; also used by visual designer.
16 | public static AppBuilder BuildAvaloniaApp()
17 | => AppBuilder.Configure()
18 | .UsePlatformDetect()
19 | .WithInterFont()
20 | .LogToTrace();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/AvaloniaSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net8.0
6 | enable
7 | true
8 | app.manifest
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/mapbox.vector.tile.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 12
6 | enable
7 | 5.3.0
8 | 5.3.0
9 | 5.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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/README.md:
--------------------------------------------------------------------------------
1 | # VelloAvaloniaSample
2 |
3 | An Avalonia-based sample application that demonstrates rendering Mapbox Vector Tiles using Avalonia's native drawing APIs.
4 |
5 | ## Description
6 |
7 | This cross-platform desktop application uses Avalonia UI framework with its native 2D graphics rendering to visualize vector tile data from a `.pbf` (Protobuf) file. It reads cadastral data and renders the geometries as blue lines on a white canvas.
8 |
9 | ## Features
10 |
11 | - Cross-platform support (Windows, macOS, Linux) via Avalonia
12 | - Avalonia's native DrawingContext for 2D graphics rendering
13 | - Reads and parses Mapbox Vector Tile format (.pbf files)
14 | - Visualizes vector tile geometries
15 |
16 | ## Requirements
17 |
18 | - .NET 8.0 or later
19 | - Avalonia 11.3.6
20 |
21 | ## Running the Sample
22 |
23 | ```bash
24 | cd samples/VelloAvaloniaSample
25 | dotnet run
26 | ```
27 |
28 | ## Project Structure
29 |
30 | - `Program.cs` - Application entry point
31 | - `App.axaml` / `App.axaml.cs` - Avalonia application configuration
32 | - `MainWindow.axaml` / `MainWindow.axaml.cs` - Main window with custom rendering
33 | - `cadastral.pbf` - Sample vector tile data file
34 |
35 | ## How It Works
36 |
37 | The application uses a custom `VectorTileCanvas` that extends Avalonia's `Control` class. It:
38 |
39 | 1. Reads the vector tile data from `cadastral.pbf`
40 | 2. Parses the tile using `VectorTileParser.Parse()`
41 | 3. Overrides the `Render` method to draw geometries using Avalonia's native `DrawingContext`
42 |
43 | This sample demonstrates cross-platform vector tile rendering using Avalonia's built-in 2D drawing capabilities.
44 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/VectorTileEncoder.cs:
--------------------------------------------------------------------------------
1 | using ProtoBuf;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace Mapbox.Vector.Tile;
6 |
7 | public static class VectorTileEncoder
8 | {
9 | public static Stream Encode(List layers, Stream stream)
10 | {
11 | var tile = new Tile();
12 |
13 | foreach (var vectorTileLayer in layers)
14 | {
15 | var layer = new Tile.Layer
16 | {
17 | Name = vectorTileLayer.Name,
18 | Version = vectorTileLayer.Version,
19 | Extent = vectorTileLayer.Extent
20 | };
21 |
22 | var keys = new List();
23 | var values = new List();
24 |
25 | foreach (var vectorTileFeature in vectorTileLayer.VectorTileFeatures)
26 | {
27 | var feature = new Tile.Feature
28 | {
29 | Id = ulong.Parse(vectorTileFeature.Id),
30 | Type = vectorTileFeature.GeometryType
31 | };
32 |
33 | // Encode attributes
34 | var tags = AttributesEncoder.Encode(vectorTileFeature.Attributes, keys, values);
35 | feature.Tags.AddRange(tags);
36 |
37 | // Encode geometry
38 | var geometry = GeometryEncoder.EncodeGeometry(vectorTileFeature.Geometry, vectorTileFeature.GeometryType);
39 | feature.Geometry.AddRange(geometry);
40 |
41 | layer.Features.Add(feature);
42 | }
43 |
44 | layer.Keys.AddRange(keys);
45 | layer.Values.AddRange(values);
46 |
47 | tile.Layers.Add(layer);
48 | }
49 |
50 | Serializer.Serialize(stream, tile);
51 | return stream;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/RealTileRoundTripTest.cs:
--------------------------------------------------------------------------------
1 | using Mapbox.Vector.Tile;
2 | using NUnit.Framework;
3 | using System.IO;
4 |
5 | namespace mapbox.vector.tile.tests;
6 |
7 | public class RealTileRoundTripTest
8 | {
9 | [Test]
10 | public void TestRealTileRoundTrip()
11 | {
12 | // Test that we can decode a real tile and encode it back
13 | var pbfFile = Path.Combine("testdata", "14-8801-5371.vector.pbf");
14 |
15 | // Decode the original tile
16 | var originalStream = File.OpenRead(pbfFile);
17 | var decodedLayers = VectorTileParser.Parse(originalStream);
18 | originalStream.Close();
19 |
20 | Assert.That(decodedLayers.Count, Is.GreaterThan(0), "Should have decoded at least one layer");
21 |
22 | // Encode it back
23 | var encodedStream = new MemoryStream();
24 | VectorTileEncoder.Encode(decodedLayers, encodedStream);
25 | encodedStream.Seek(0, SeekOrigin.Begin);
26 |
27 | Assert.That(encodedStream.Length, Is.GreaterThan(0), "Encoded stream should not be empty");
28 |
29 | // Decode the encoded tile
30 | var reDecodedLayers = VectorTileParser.Parse(encodedStream);
31 |
32 | // Verify layer count matches
33 | Assert.That(reDecodedLayers.Count, Is.EqualTo(decodedLayers.Count), "Layer count should match");
34 |
35 | // Verify each layer
36 | for (int i = 0; i < decodedLayers.Count; i++)
37 | {
38 | var original = decodedLayers[i];
39 | var reDecoded = reDecodedLayers[i];
40 |
41 | Assert.That(reDecoded.Name, Is.EqualTo(original.Name), $"Layer {i} name should match");
42 | Assert.That(reDecoded.VectorTileFeatures.Count, Is.EqualTo(original.VectorTileFeatures.Count),
43 | $"Layer '{original.Name}' feature count should match");
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/samples/AvaloniaSample/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Media;
4 | using Mapbox.Vector.Tile;
5 | using System.IO;
6 |
7 | namespace VelloAvaloniaSample
8 | {
9 | public partial class MainWindow : Window
10 | {
11 | public MainWindow()
12 | {
13 | InitializeComponent();
14 |
15 | // Create a simple VectorTileCanvas for rendering
16 | var velloControl = new VectorTileCanvas();
17 | SkiaPanel.Children.Add(velloControl);
18 | }
19 | }
20 |
21 | // Custom canvas control that renders vector tiles using Avalonia's native drawing
22 | public class VectorTileCanvas : Control
23 | {
24 | private VectorTileLayer? _tileData;
25 |
26 | public VectorTileCanvas()
27 | {
28 | LoadTileData();
29 | }
30 |
31 | private void LoadTileData()
32 | {
33 | const string vtfile = @"cadastral.pbf";
34 | if (File.Exists(vtfile))
35 | {
36 | using var stream = File.OpenRead(vtfile);
37 | var layerInfos = VectorTileParser.Parse(stream);
38 | if (layerInfos.Count > 0)
39 | {
40 | _tileData = layerInfos[0];
41 | }
42 | }
43 | }
44 |
45 | public override void Render(DrawingContext context)
46 | {
47 | base.Render(context);
48 |
49 | if (_tileData == null) return;
50 |
51 | // Draw white background
52 | context.FillRectangle(
53 | Brushes.White,
54 | new Rect(0, 0, Bounds.Width, Bounds.Height)
55 | );
56 |
57 | // Create a blue pen for drawing lines
58 | var pen = new Pen(Brushes.Blue, 2);
59 |
60 | // Render vector tile features
61 | foreach (var feature in _tileData.VectorTileFeatures)
62 | {
63 | if (feature.Geometry == null || feature.Geometry.Count == 0)
64 | continue;
65 |
66 | var coords = feature.Geometry[0];
67 | for (var i = 1; i < coords.Count; i++)
68 | {
69 | var c0 = coords[i - 1];
70 | var c1 = coords[i];
71 |
72 | // Draw line using Avalonia's native drawing
73 | context.DrawLine(
74 | pen,
75 | new Point(c0.X, c0.Y),
76 | new Point(c1.X, c1.Y)
77 | );
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/GeometryEncoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Mapbox.Vector.Tile;
6 |
7 | public static class GeometryEncoder
8 | {
9 | private enum Command
10 | {
11 | MoveTo = 1,
12 | LineTo = 2,
13 | ClosePath = 7
14 | }
15 |
16 | public static List EncodeGeometry(List> geometry, Tile.GeomType geomType)
17 | {
18 | var commands = new List();
19 | long lastX = 0;
20 | long lastY = 0;
21 |
22 | foreach (var segment in geometry)
23 | {
24 | if (segment.Count == 0)
25 | continue;
26 |
27 | // Access the underlying array and use offset
28 | var array = segment.Array!;
29 | var offset = segment.Offset;
30 | var count = segment.Count;
31 |
32 | // MoveTo command for first point
33 | var firstPoint = array[offset];
34 | commands.Add(EncodeCommand(Command.MoveTo, 1));
35 |
36 | var dx = firstPoint.X - lastX;
37 | var dy = firstPoint.Y - lastY;
38 | commands.Add((uint)ZigZag.Encode(dx));
39 | commands.Add((uint)ZigZag.Encode(dy));
40 |
41 | lastX = firstPoint.X;
42 | lastY = firstPoint.Y;
43 |
44 | // LineTo commands for remaining points (excluding last if it's a duplicate of first)
45 | var lineToCount = count - 1;
46 |
47 | // For polygons, check if last point equals first point
48 | if (geomType == Tile.GeomType.Polygon && count > 1)
49 | {
50 | var lastPoint = array[offset + count - 1];
51 | if (lastPoint.X == firstPoint.X && lastPoint.Y == firstPoint.Y)
52 | {
53 | lineToCount--;
54 | }
55 | }
56 |
57 | if (lineToCount > 0)
58 | {
59 | commands.Add(EncodeCommand(Command.LineTo, (uint)lineToCount));
60 |
61 | for (int i = 1; i <= lineToCount; i++)
62 | {
63 | var point = array[offset + i];
64 | dx = point.X - lastX;
65 | dy = point.Y - lastY;
66 | commands.Add((uint)ZigZag.Encode(dx));
67 | commands.Add((uint)ZigZag.Encode(dy));
68 |
69 | lastX = point.X;
70 | lastY = point.Y;
71 | }
72 | }
73 |
74 | // ClosePath command for polygons only
75 | if (geomType == Tile.GeomType.Polygon)
76 | {
77 | commands.Add(EncodeCommand(Command.ClosePath, 1));
78 | }
79 | }
80 |
81 | return commands;
82 | }
83 |
84 | private static uint EncodeCommand(Command command, uint count)
85 | {
86 | return (count << 3) | (uint)command;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/mapbox-vector-tile.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 18
4 | VisualStudioVersion = 18.0.11123.170
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}") = "AvaloniaSample", "samples\AvaloniaSample\AvaloniaSample.csproj", "{B973CFAC-BE3E-48FD-A4E8-5F0609C89F31}"
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 | {B973CFAC-BE3E-48FD-A4E8-5F0609C89F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {B973CFAC-BE3E-48FD-A4E8-5F0609C89F31}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {B973CFAC-BE3E-48FD-A4E8-5F0609C89F31}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {B973CFAC-BE3E-48FD-A4E8-5F0609C89F31}.Release|Any CPU.Build.0 = Release|Any CPU
49 | EndGlobalSection
50 | GlobalSection(SolutionProperties) = preSolution
51 | HideSolutionNode = FALSE
52 | EndGlobalSection
53 | GlobalSection(NestedProjects) = preSolution
54 | {3ADBC272-EDCC-4688-A17B-3840B20BDB17} = {DA8D94F6-E16A-480B-B25D-4002F7F432C9}
55 | {B973CFAC-BE3E-48FD-A4E8-5F0609C89F31} = {DA8D94F6-E16A-480B-B25D-4002F7F432C9}
56 | EndGlobalSection
57 | GlobalSection(ExtensibilityGlobals) = postSolution
58 | SolutionGuid = {EAE9C612-1C61-4448-9730-9C3F6D1CA715}
59 | EndGlobalSection
60 | EndGlobal
61 |
--------------------------------------------------------------------------------
/src/AttributesEncoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Mapbox.Vector.Tile;
5 |
6 | public static class AttributesEncoder
7 | {
8 | public static List Encode(List> attributes, List keys, List values)
9 | {
10 | var tags = new List();
11 |
12 | foreach (var attribute in attributes)
13 | {
14 | var key = attribute.Key;
15 | var value = attribute.Value;
16 |
17 | // Get or add key index
18 | var keyIndex = keys.IndexOf(key);
19 | if (keyIndex == -1)
20 | {
21 | keyIndex = keys.Count;
22 | keys.Add(key);
23 | }
24 |
25 | // Get or add value index
26 | var tileValue = CreateTileValue(value);
27 | var valueIndex = FindValueIndex(values, tileValue);
28 | if (valueIndex == -1)
29 | {
30 | valueIndex = values.Count;
31 | values.Add(tileValue);
32 | }
33 |
34 | tags.Add((uint)keyIndex);
35 | tags.Add((uint)valueIndex);
36 | }
37 |
38 | return tags;
39 | }
40 |
41 | private static Tile.Value CreateTileValue(object value)
42 | {
43 | var tileValue = new Tile.Value();
44 |
45 | switch (value)
46 | {
47 | case string stringValue:
48 | tileValue.StringValue = stringValue;
49 | break;
50 | case bool boolValue:
51 | tileValue.BoolValue = boolValue;
52 | break;
53 | case float floatValue:
54 | tileValue.FloatValue = floatValue;
55 | break;
56 | case double doubleValue:
57 | tileValue.DoubleValue = doubleValue;
58 | break;
59 | case int intValue:
60 | tileValue.IntValue = intValue;
61 | break;
62 | case long longValue:
63 | tileValue.IntValue = longValue;
64 | break;
65 | case uint uintValue:
66 | tileValue.UintValue = uintValue;
67 | break;
68 | case ulong ulongValue:
69 | tileValue.UintValue = ulongValue;
70 | break;
71 | default:
72 | throw new NotImplementedException($"Unsupported attribute type: {value.GetType()}");
73 | }
74 |
75 | return tileValue;
76 | }
77 |
78 | private static int FindValueIndex(List values, Tile.Value newValue)
79 | {
80 | for (int i = 0; i < values.Count; i++)
81 | {
82 | var existingValue = values[i];
83 | if (ValuesEqual(existingValue, newValue))
84 | {
85 | return i;
86 | }
87 | }
88 | return -1;
89 | }
90 |
91 | private static bool ValuesEqual(Tile.Value a, Tile.Value b)
92 | {
93 | if (a.HasStringValue && b.HasStringValue)
94 | return a.StringValue == b.StringValue;
95 | if (a.HasBoolValue && b.HasBoolValue)
96 | return a.BoolValue == b.BoolValue;
97 | if (a.HasFloatValue && b.HasFloatValue)
98 | return Math.Abs(a.FloatValue - b.FloatValue) < 0.0001f;
99 | if (a.HasDoubleValue && b.HasDoubleValue)
100 | return Math.Abs(a.DoubleValue - b.DoubleValue) < 0.0001;
101 | if (a.HasIntValue && b.HasIntValue)
102 | return a.IntValue == b.IntValue;
103 | if (a.HasSIntValue && b.HasSIntValue)
104 | return a.SintValue == b.SintValue;
105 | if (a.HasUIntValue && b.HasUIntValue)
106 | return a.UintValue == b.UintValue;
107 |
108 | return false;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mapbox-vector-tile-cs
2 |
3 | [](https://www.nuget.org/packages/mapbox-vector-tile/) 
4 |
5 | .NET Standard 2.0 library for encoding and decoding Mapbox vector tiles.
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 | ### Decoding
20 |
21 | ```cs
22 | const string vtfile = "vectortile.pbf";
23 | var stream = File.OpenRead(vtfile);
24 | var layerInfos = VectorTileParser.Parse(stream);
25 | ```
26 |
27 | ### Encoding
28 |
29 | ```cs
30 | // Create a layer
31 | var layer = new VectorTileLayer("my_layer", 2, 4096);
32 |
33 | // Create a feature with attributes and geometry
34 | var attributes = new List>
35 | {
36 | new KeyValuePair("name", "Example"),
37 | new KeyValuePair("value", 42)
38 | };
39 |
40 | var coordinates = new[] { new Coordinate(100, 200) };
41 | var geometry = new List>
42 | {
43 | new ArraySegment(coordinates)
44 | };
45 |
46 | var feature = new VectorTileFeature("1", geometry, attributes, Tile.GeomType.Point, 4096);
47 | layer.VectorTileFeatures.Add(feature);
48 |
49 | // Encode to stream
50 | var layers = new List { layer };
51 | var stream = VectorTileEncoder.Encode(layers, new MemoryStream());
52 |
53 | // Save to file
54 | File.WriteAllBytes("output.pbf", ((MemoryStream)stream).ToArray());
55 | ```
56 |
57 | Tip: If you use this library with vector tiles loading from a webserver, you could run into the following exception:
58 | 'ProtoBuf.ProtoException: Invalid wire-type; this usually means you have over-written a file without truncating or setting the length'
59 | Probably you need to check the GZip compression, see also TileParserTests.cs for an example.
60 |
61 | ## Building
62 |
63 | ```
64 | $ git clone https://github.com/bertt/mapbox-vector-tile-cs.git
65 | $ cd mapbox-vector-tile-cs
66 | $ dotnet build
67 | ```
68 |
69 | ## Testing
70 |
71 | ```
72 | $ git clone https://github.com/bertt/mapbox-vector-tile-cs.git
73 | $ cd mapbox-vector-tile-cs/tests/mapbox.vector.tile.tests
74 | $ dotnet test
75 | Passed!
76 | Failed: 0, Passed: 38, Skipped: 0, Total: 38, Duration: 937 ms
77 | ```
78 |
79 | ## Samples
80 |
81 | 1] GeoJSON
82 |
83 | The samples folder contains a simple console application that reads a vector tile from a file and converts to GeoJSON file.
84 |
85 | 2] SkiaSharp Windows Forms Sample
86 |
87 | SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. The samples folder contains a simple Windows Forms GUI application that reads a
88 | vector tile from a file and draws the geometries using SkiaSharp.
89 |
90 | 3] Avalonia Sample
91 |
92 | The samples folder contains a cross-platform GUI application built with Avalonia UI that reads a vector tile from a file and draws the geometries using Avalonia's native DrawingContext API.
93 | This sample works on Windows, macOS, and Linux.
94 |
95 | ## Benchmarking
96 |
97 | Test performed with Mapbox vector tile '14-8801-5371.vector.pbf'
98 |
99 | Layers used:
100 |
101 | Point layer: parks (id=17) - 558 features
102 |
103 | Line layer: roads (id=8) - 686 features
104 |
105 | Polygon layer: building (id=5) - 975 features
106 |
107 | ```
108 |
109 | | Method | Mean | Error | StdDev |
110 | |-------------------------- |---------:|----------:|----------:|
111 | | ParseVectorTileFromStream | 1.401 us | 0.0133 us | 0.0125 us |
112 | ```
113 |
114 | ## History
115 |
116 | 2025-11-13: release 5.3.0, add encoding vector tile
117 |
118 | 2025-04-16: Release 5.2.3, updated licence in Nuget package
119 |
120 | 2025-02-25: Release 5.2.2, nullable enabled
121 |
122 | 2025-02-24: Release 5.2.1, improved memory handling
123 |
124 | 2025-02-24: Release 5.2, library is now .NET Standard 2.0
125 |
126 | 2025-01-29: Release 5.1, upgrading dependencies + remove GeoJSON.NET dependency + adding samples (SkiaSharp, GeoJSON)
127 |
128 | 2023-11-26: Release 5.0.2, containing .NET 8
129 |
130 | 2022-10-29: Release 5.0.1, upgrading dependencies
131 |
132 | 2022-10-29: Release 5.0 containing .NET 6
133 |
134 | 2018-08-28: Release 4.2 containing .NET Standard 2.0
135 |
136 | 2018-03-08: Release 4.1 with fix for issue 16 (https://github.com/bertt/mapbox-vector-tile-cs/issues/16 - about serializing attributes)
137 |
138 | 2017-10-19: Release 4.0 for .NET Standard 1.3
139 |
140 | 2016-11-03: Release 3.1
141 |
142 | Changes: Add support for polygon inner- and outerrings
143 |
144 | 2016-10-31: Release 3.0
145 |
146 | Changes: Add support for multi-geometries
147 |
148 | 2015-07-08: Release 2.0
149 |
150 | 2015-07-07: Release 1.0
151 |
152 |
153 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/VectorTileEncoderTests.cs:
--------------------------------------------------------------------------------
1 | using Mapbox.Vector.Tile;
2 | using NUnit.Framework;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 |
7 | namespace mapbox.vector.tile.tests;
8 |
9 | public class VectorTileEncoderTests
10 | {
11 | [Test]
12 | public void TestEncodeDecodeRoundTrip()
13 | {
14 | // Create a simple vector tile layer with a point feature
15 | var layer = new VectorTileLayer("test_layer", 2, 4096);
16 |
17 | var attributes = new List>
18 | {
19 | new KeyValuePair("name", "Test Point"),
20 | new KeyValuePair("value", 42)
21 | };
22 |
23 | var coordinates = new[]
24 | {
25 | new Coordinate(100, 200)
26 | };
27 |
28 | var geometry = new List>
29 | {
30 | new ArraySegment(coordinates)
31 | };
32 |
33 | var feature = new VectorTileFeature("1", geometry, attributes, Tile.GeomType.Point, 4096);
34 | layer.VectorTileFeatures.Add(feature);
35 |
36 | var layers = new List { layer };
37 |
38 | // Encode to stream
39 | var stream = new MemoryStream();
40 | VectorTileEncoder.Encode(layers, stream);
41 |
42 | // Decode from stream
43 | stream.Seek(0, SeekOrigin.Begin);
44 | var decodedLayers = VectorTileParser.Parse(stream);
45 |
46 | // Verify
47 | Assert.That(decodedLayers.Count, Is.EqualTo(1));
48 | Assert.That(decodedLayers[0].Name, Is.EqualTo("test_layer"));
49 | Assert.That(decodedLayers[0].VectorTileFeatures.Count, Is.EqualTo(1));
50 |
51 | var decodedFeature = decodedLayers[0].VectorTileFeatures[0];
52 | Assert.That(decodedFeature.GeometryType, Is.EqualTo(Tile.GeomType.Point));
53 | Assert.That(decodedFeature.Attributes.Count, Is.EqualTo(2));
54 | }
55 |
56 | [Test]
57 | public void TestEncodeDecodeLineString()
58 | {
59 | var layer = new VectorTileLayer("roads", 2, 4096);
60 |
61 | var attributes = new List>
62 | {
63 | new KeyValuePair("type", "highway")
64 | };
65 |
66 | var coordinates = new[]
67 | {
68 | new Coordinate(0, 0),
69 | new Coordinate(100, 0),
70 | new Coordinate(100, 100),
71 | new Coordinate(0, 100),
72 | new Coordinate(0, 0)
73 | };
74 |
75 | var geometry = new List>
76 | {
77 | new ArraySegment(coordinates)
78 | };
79 |
80 | var feature = new VectorTileFeature("1", geometry, attributes, Tile.GeomType.LineString, 4096);
81 | layer.VectorTileFeatures.Add(feature);
82 |
83 | var layers = new List { layer };
84 |
85 | // Encode to stream
86 | var stream = new MemoryStream();
87 | VectorTileEncoder.Encode(layers, stream);
88 |
89 | // Decode from stream
90 | stream.Seek(0, SeekOrigin.Begin);
91 | var decodedLayers = VectorTileParser.Parse(stream);
92 |
93 | // Verify
94 | Assert.That(decodedLayers.Count, Is.EqualTo(1));
95 | Assert.That(decodedLayers[0].Name, Is.EqualTo("roads"));
96 | Assert.That(decodedLayers[0].VectorTileFeatures.Count, Is.EqualTo(1));
97 |
98 | var decodedFeature = decodedLayers[0].VectorTileFeatures[0];
99 | Assert.That(decodedFeature.GeometryType, Is.EqualTo(Tile.GeomType.LineString));
100 | Assert.That(decodedFeature.Geometry.Count, Is.EqualTo(1));
101 | }
102 |
103 | [Test]
104 | public void TestEncodeDecodePolygon()
105 | {
106 | var layer = new VectorTileLayer("buildings", 2, 4096);
107 |
108 | var attributes = new List>
109 | {
110 | new KeyValuePair("name", "Building A"),
111 | new KeyValuePair("height", 25.5)
112 | };
113 |
114 | var coordinates = new[]
115 | {
116 | new Coordinate(0, 0),
117 | new Coordinate(100, 0),
118 | new Coordinate(100, 100),
119 | new Coordinate(0, 100),
120 | new Coordinate(0, 0)
121 | };
122 |
123 | var geometry = new List>
124 | {
125 | new ArraySegment(coordinates)
126 | };
127 |
128 | var feature = new VectorTileFeature("1", geometry, attributes, Tile.GeomType.Polygon, 4096);
129 | layer.VectorTileFeatures.Add(feature);
130 |
131 | var layers = new List { layer };
132 |
133 | // Encode to stream
134 | var stream = new MemoryStream();
135 | VectorTileEncoder.Encode(layers, stream);
136 |
137 | // Decode from stream
138 | stream.Seek(0, SeekOrigin.Begin);
139 | var decodedLayers = VectorTileParser.Parse(stream);
140 |
141 | // Verify
142 | Assert.That(decodedLayers.Count, Is.EqualTo(1));
143 | Assert.That(decodedLayers[0].Name, Is.EqualTo("buildings"));
144 | Assert.That(decodedLayers[0].VectorTileFeatures.Count, Is.EqualTo(1));
145 |
146 | var decodedFeature = decodedLayers[0].VectorTileFeatures[0];
147 | Assert.That(decodedFeature.GeometryType, Is.EqualTo(Tile.GeomType.Polygon));
148 | Assert.That(decodedFeature.Attributes.Count, Is.EqualTo(2));
149 | }
150 |
151 | [Test]
152 | public void TestEncodeMultipleFeatures()
153 | {
154 | var layer = new VectorTileLayer("mixed", 2, 4096);
155 |
156 | // Add a point
157 | var pointCoords = new[] { new Coordinate(50, 50) };
158 | var pointGeometry = new List> { new ArraySegment(pointCoords) };
159 | var pointAttrs = new List> { new KeyValuePair("type", "marker") };
160 | var pointFeature = new VectorTileFeature("1", pointGeometry, pointAttrs, Tile.GeomType.Point, 4096);
161 | layer.VectorTileFeatures.Add(pointFeature);
162 |
163 | // Add a line
164 | var lineCoords = new[] { new Coordinate(0, 0), new Coordinate(100, 100), new Coordinate(0, 0) };
165 | var lineGeometry = new List> { new ArraySegment(lineCoords) };
166 | var lineAttrs = new List> { new KeyValuePair("type", "path") };
167 | var lineFeature = new VectorTileFeature("2", lineGeometry, lineAttrs, Tile.GeomType.LineString, 4096);
168 | layer.VectorTileFeatures.Add(lineFeature);
169 |
170 | var layers = new List { layer };
171 |
172 | // Encode to stream
173 | var stream = new MemoryStream();
174 | VectorTileEncoder.Encode(layers, stream);
175 |
176 | // Decode from stream
177 | stream.Seek(0, SeekOrigin.Begin);
178 | var decodedLayers = VectorTileParser.Parse(stream);
179 |
180 | // Verify
181 | Assert.That(decodedLayers.Count, Is.EqualTo(1));
182 | Assert.That(decodedLayers[0].VectorTileFeatures.Count, Is.EqualTo(2));
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------