Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Improvements for vector tiles #8

Merged
merged 11 commits into from
May 19, 2020
42 changes: 42 additions & 0 deletions .github/workflows/dotnetcore.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: .NET Core

on:
push:
branches:
- develop
pull_request:
branches:
- develop

jobs:
build:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ]

steps:
- name: Get source
uses: actions/checkout@v1

- name: Setup .NET Core 3.1
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.1.100

- name: Build
run: dotnet build -c Release -v minimal -p:WarningLevel=3

- name: Test
run: dotnet test -c Release --no-build

- name: Pack
run: dotnet pack -c Release --no-build -o artifacts -p:NoWarn=NU5105

- name: Upload
uses: actions/upload-artifact@v1
with:
name: NuGet Package Files (${{ matrix.os }})
path: artifacts
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# NetTopologySuite.IO.VectorTiles

[![Build status](http://build.itinero.tech:8080/app/rest/builds/buildType:(id:OsmSharp_CoreDevelop)/statusIcon)](https://build.itinero.tech/viewType.html?buildTypeId=OsmSharp_CoreDevelop)
![.NET Core](https://github.com/FObermaier/NetTopologySuite.IO.VectorTiles/workflows/.NET%20Core/badge.svg)

[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/OsmSharp/core/blob/develop/LICENSE.md)

A package that can be used to generate vector tiles using NTS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ internal static class DictionaryExtensions
/// <param name="dic">The dictionary.</param>
/// <param name="key">The key.</param>
/// <returns></returns>
public static uint AddOrGet(this Dictionary<string, uint> dic, string key)
public static uint AddOrGet<TKey>(this Dictionary<TKey, uint> dic, TKey key)
{
if (dic.TryGetValue(key, out var keyId)) return keyId;
if (dic.TryGetValue(key, out uint keyId))
return keyId;
keyId = (uint)dic.Count;
dic[key] = keyId;
return keyId;
}
}
}
}
23 changes: 23 additions & 0 deletions src/NetTopologySuite.IO.VectorTiles.Mapbox/MapboxCommandType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace NetTopologySuite.IO.VectorTiles.Mapbox
{
/// <summary>
/// <a href="https://github.com/mapbox/vector-tile-spec/tree/master/2.1#433-command-types">Command Types</a>
/// </summary>
public enum MapboxCommandType
{
/// <summary>
/// <a href="https://github.com/mapbox/vector-tile-spec/tree/master/2.1#4331-moveto-command">MoveTo Command</a>
/// </summary>
MoveTo = 1,

/// <summary>
/// <a href="https://github.com/mapbox/vector-tile-spec/tree/master/2.1#4332-lineto-command">LineTo Command</a>
/// </summary>
LineTo = 2,

/// <summary>
/// <a href="https://github.com/mapbox/vector-tile-spec/tree/master/2.1#4333-closepath-command">ClosePath Command</a>
/// </summary>
ClosePath = 7,
}
}
305 changes: 305 additions & 0 deletions src/NetTopologySuite.IO.VectorTiles.Mapbox/MapboxTileReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Xml;
using GeoAPI.Geometries;
using NetTopologySuite.Features;

namespace NetTopologySuite.IO.VectorTiles.Mapbox
{
public class MapboxTileReader
{


private readonly IGeometryFactory _factory;

public MapboxTileReader() : this(GeoAPI.GeometryServiceProvider.Instance.CreateGeometryFactory(4326))
{
}

public MapboxTileReader(IGeometryFactory factory)
{
_factory = factory;
}

public VectorTile Read(Stream stream, Tiles.Tile tileDefinition)
{


// Deserialize the tile
var tile = ProtoBuf.Serializer.Deserialize<Mapbox.Tile>(stream);

var vectorTile = new VectorTile { TileId = tileDefinition.Id };
foreach (var mbTileLayer in tile.Layers)
{
Debug.Assert(mbTileLayer.Version == 2U);

var tgs = new TileGeometryTransform(tileDefinition, mbTileLayer.Extent);
var layer = new Layer {Name = mbTileLayer.Name};
foreach (var mbTileFeature in mbTileLayer.Features)
{
var feature = ReadFeature(tgs, mbTileLayer, mbTileFeature);
layer.Features.Add(feature);
}
vectorTile.Layers.Add(layer);
}

return vectorTile;
}

private IFeature ReadFeature(TileGeometryTransform tgs, Tile.Layer mbTileLayer, Tile.Feature mbTileFeature)
{
var geometry = ReadGeometry(tgs, mbTileFeature.Type, mbTileFeature.Geometry);
var attributes = ReadAttributeTable(mbTileFeature, mbTileLayer.Keys, mbTileLayer.Values);
return new Feature(geometry, attributes);
}

private IGeometry ReadGeometry(TileGeometryTransform tgs, Tile.GeomType type, IList<uint> geometry)
{
switch (type)
{
case Tile.GeomType.Point:
return ReadPoint(tgs, geometry);

case Tile.GeomType.LineString:
return ReadLineString(tgs, geometry);

case Tile.GeomType.Polygon:
return ReadPolygon(tgs, geometry);
}

return null;
}

private IGeometry ReadPoint(TileGeometryTransform tgs, IList<uint> geometry)
{
int currentIndex = 0; int currentX = 0; int currentY = 0;
var sequences = ReadCoordinateSequences(tgs, geometry, ref currentIndex, ref currentX, ref currentY, forPoint:true);
return CreatePuntal(sequences);
}

private IGeometry ReadLineString(TileGeometryTransform tgs, IList<uint> geometry)
{
int currentIndex = 0; int currentX = 0; int currentY = 0;
var sequences = ReadCoordinateSequences(tgs, geometry, ref currentIndex, ref currentX, ref currentY);
return CreateLineal(sequences);
}

private IGeometry ReadPolygon(TileGeometryTransform tgs, IList<uint> geometry)
{
int currentIndex = 0; int currentX = 0; int currentY = 0;
var sequences = ReadCoordinateSequences(tgs, geometry, ref currentIndex, ref currentX, ref currentY, 1);
return CreatePolygonal(sequences);
}

private IGeometry CreatePuntal(ICoordinateSequence[] sequences)
{
if (sequences == null || sequences.Length == 0)
return null;

var points = new IPoint[sequences.Length];
for (int i = 0; i < sequences.Length; i++)
points[i] = _factory.CreatePoint(sequences[i]);

if (points.Length == 1)
return points[0];

return _factory.CreateMultiPoint(points);
}

private IGeometry CreateLineal(ICoordinateSequence[] sequences)
{
if (sequences == null || sequences.Length == 0)
return null;

var lineStrings = new ILineString[sequences.Length];
for (int i = 0; i < sequences.Length; i++)
lineStrings[i] = _factory.CreateLineString(sequences[i]);

if (lineStrings.Length == 1)
return lineStrings[0];

return _factory.CreateMultiLineString(lineStrings);
}

private IGeometry CreatePolygonal(ICoordinateSequence[] sequences)
{
List<IPolygon> polygons = new List<IPolygon>();

ILinearRing shell = null;
List<ILinearRing> holes = new List<ILinearRing>();

for (int i = 0; i < sequences.Length; i++)
{
var ring = _factory.CreateLinearRing(sequences[i]);
if (ring.IsCCW)
{
if (shell != null)
{
polygons.Add(_factory.CreatePolygon(shell, holes.ToArray()));
holes.Clear();
}
shell = ring;
}
else
{
if (shell == null)
throw new InvalidOperationException();
holes.Add(ring);
}
}

polygons.Add(_factory.CreatePolygon(shell, holes.ToArray()));

if (polygons.Count == 1)
return polygons[0];

return _factory.CreateMultiPolygon(polygons.ToArray());
}

private ICoordinateSequence[] ReadCoordinateSequences(
TileGeometryTransform tgs, IList<uint> geometry,
ref int currentIndex, ref int currentX, ref int currentY, int buffer = 0, bool forPoint = false)
{
(var command, int count) = ParseCommandInteger(geometry[currentIndex]);
Debug.Assert(command == MapboxCommandType.MoveTo);
if (count > 1)
{
currentIndex++;
return ReadSinglePointSequences(tgs, geometry, count, ref currentIndex, ref currentX, ref currentY);
}

var sequences = new List<ICoordinateSequence>();
var currentPosition = (currentX, currentY);
while (currentIndex < geometry.Count)
{
(command, count) = ParseCommandInteger(geometry[currentIndex++]);
Debug.Assert(command == MapboxCommandType.MoveTo);
Debug.Assert(count == 1);

// Read the current position
currentPosition = ParseOffset(currentPosition, geometry, ref currentIndex);

if (!forPoint)
{
// Read the next command (should be LineTo)
(command, count) = ParseCommandInteger(geometry[currentIndex++]);
if (command != MapboxCommandType.LineTo) count = 0;
}
else
{
count = 0;
}

// Create sequence, add starting point
var sequence = _factory.CoordinateSequenceFactory.Create(1 + count + buffer, 2);
int sequenceIndex = 0;
TransformOffsetAndAddToSequence(tgs, currentPosition, sequence, sequenceIndex++);

// Read and add offsets
for (int i = 1; i <= count; i++)
{
currentPosition = ParseOffset(currentPosition, geometry, ref currentIndex);
TransformOffsetAndAddToSequence(tgs, currentPosition, sequence, sequenceIndex++);
}

// Check for ClosePath command
if (currentIndex < geometry.Count)
{
(command, _) = ParseCommandInteger(geometry[currentIndex]);
if (command == MapboxCommandType.ClosePath)
{
Debug.Assert(buffer > 0);
sequence.SetOrdinate(sequenceIndex, Ordinate.X, sequence.GetOrdinate(0, Ordinate.X));
sequence.SetOrdinate(sequenceIndex, Ordinate.Y, sequence.GetOrdinate(0, Ordinate.Y));

currentIndex++;
sequenceIndex++;
}
}

Debug.Assert(sequenceIndex == sequence.Count);

sequences.Add(sequence);
}

// update current position values
currentX = currentPosition.currentX;
currentY = currentPosition.currentY;

return sequences.ToArray();
}

private ICoordinateSequence[] ReadSinglePointSequences(TileGeometryTransform tgs, IList<uint> geometry,
int numSequences, ref int currentIndex, ref int currentX, ref int currentY)
{
var res = new ICoordinateSequence[numSequences];
var currentPosition = (currentX, currentY);
for (int i = 0; i < numSequences; i++)
{
res[i] = _factory.CoordinateSequenceFactory.Create(1, 2);

currentPosition = ParseOffset(currentPosition, geometry, ref currentIndex);
TransformOffsetAndAddToSequence(tgs, currentPosition, res[i], 0);
}

currentX = currentPosition.currentX;
currentY = currentPosition.currentY;
return res;
}

private void TransformOffsetAndAddToSequence(TileGeometryTransform tgs, (int x, int y) localPosition, ICoordinateSequence sequence, int index)
{
sequence.SetOrdinate(index, Ordinate.X, tgs.Left + localPosition.x * tgs.LongitudeStep);
sequence.SetOrdinate(index, Ordinate.Y, tgs.Top - localPosition.y * tgs.LatitudeStep);
}

private (int, int) ParseOffset((int x, int y) currentPosition, IList<uint> parameterIntegers, ref int offset)
{
return (currentPosition.x + Decode(parameterIntegers[offset++]),
currentPosition.y + Decode(parameterIntegers[offset++]));
}

private static int Decode(uint parameterInteger)
{
return ((int) (parameterInteger >> 1) ^ ((int)-(parameterInteger & 1)));
}

private static (MapboxCommandType, int) ParseCommandInteger(uint commandInteger)
{
return unchecked(((MapboxCommandType) (commandInteger & 0x07U), (int)(commandInteger >> 3)));

}

private static IAttributesTable ReadAttributeTable(Tile.Feature mbTileFeature, List<string> keys, List<Tile.Value> values)
{
var att = new AttributesTable();
for (int i = 0; i < mbTileFeature.Tags.Count; i += 2)
{
string key = keys[(int)mbTileFeature.Tags[i]];
var value = values[(int) mbTileFeature.Tags[i + 1]];
if (value.HasBoolValue)
att.Add(key, value.BoolValue);
else if (value.HasDoubleValue)
att.Add(key, value.DoubleValue);
else if (value.HasFloatValue)
att.Add(key, value.FloatValue);
else if (value.HasIntValue)
att.Add(key, value.IntValue);
else if (value.HasSIntValue)
att.Add(key, value.SintValue);
else if (value.HasStringValue)
att.Add(key, value.StringValue);
else if (value.HasUIntValue)
att.Add(key, value.UintValue);
else
att.Add(key, null);
}

return att;
}
}
}
Loading