-
Notifications
You must be signed in to change notification settings - Fork 37
/
VectorTileReader.cs
437 lines (392 loc) · 13.7 KB
/
VectorTileReader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Text;
using Mapbox.VectorTile.Contants;
using Mapbox.VectorTile.Geometry;
using Mapbox.VectorTile.InteralClipperLib;
#if !NET20
using System.Linq;
#endif
namespace Mapbox.VectorTile {
using Polygon = List<InternalClipper.IntPoint>;
using Polygons = List<List<InternalClipper.IntPoint>>;
/// <summary>
/// Mail vector tile reader class
/// </summary>
public class VectorTileReader {
/// <summary>
/// Initialize VectorTileReader
/// </summary>
/// <param name="data">Byte array containing the raw (already unzipped) tile data</param>
/// <param name="validate">If true, run checks if the tile contains valid data. Decreases decoding speed.</param>
public VectorTileReader(byte[] data, bool validate = true) {
if (null == data) {
throw new System.Exception("Tile data cannot be null");
}
if (data[0] == 0x1f && data[1] == 0x8b) {
throw new System.Exception("Tile data is zipped");
}
_Validate = validate;
layers(data);
}
private Dictionary<string, byte[]> _Layers = new Dictionary<string, byte[]>();
private bool _Validate;
private void layers(byte[] data) {
PbfReader tileReader = new PbfReader(data);
while (tileReader.NextByte()) {
if (_Validate) {
if (!ConstantsAsDictionary.TileType.ContainsKey(tileReader.Tag)) {
throw new System.Exception(string.Format("Unknown tile tag: {0}", tileReader.Tag));
}
}
if (tileReader.Tag == (int)TileType.Layers) {
string name = null;
byte[] layerMessage = tileReader.View();
PbfReader layerView = new PbfReader(layerMessage);
while (layerView.NextByte()) {
if (layerView.Tag == (int)LayerType.Name) {
ulong strLen = (ulong)layerView.Varint();
name = layerView.GetString(strLen);
} else {
layerView.Skip();
}
}
if (_Validate) {
if (string.IsNullOrEmpty(name)) {
throw new System.Exception("Layer missing name");
}
if (_Layers.ContainsKey(name)) {
throw new System.Exception(string.Format("Duplicate layer names: {0}", name));
}
}
_Layers.Add(name, layerMessage);
} else {
tileReader.Skip();
}
}
}
/// <summary>
/// Collection of layers contained in the tile
/// </summary>
/// <returns>Collection of layer names</returns>
public ReadOnlyCollection<string> LayerNames() {
#if NET20 || PORTABLE || WINDOWS_UWP
string[] lyrNames = new string[_Layers.Keys.Count];
_Layers.Keys.CopyTo(lyrNames, 0);
return new ReadOnlyCollection<string>(lyrNames);
#else
return _Layers.Keys.ToList().AsReadOnly();
#endif
}
/// <summary>
/// Get a tile layer by name
/// </summary>
/// <param name="layerName">Name of the layer to request</param>
/// <returns>Decoded <see cref="VectorTileLayer"/></returns>
public VectorTileLayer GetLayer(string name) {
if (!_Layers.ContainsKey(name)) {
return null;
}
return getLayer(_Layers[name]);
}
private VectorTileLayer getLayer(byte[] data) {
VectorTileLayer layer = new VectorTileLayer(data);
PbfReader layerReader = new PbfReader(layer.Data);
while (layerReader.NextByte()) {
int layerType = layerReader.Tag;
if (_Validate) {
if (!ConstantsAsDictionary.LayerType.ContainsKey(layerType)) {
throw new System.Exception(string.Format("Unknown layer type: {0}", layerType));
}
}
switch ((LayerType)layerType) {
case LayerType.Version:
ulong version = (ulong)layerReader.Varint();
layer.Version = version;
break;
case LayerType.Name:
ulong strLength = (ulong)layerReader.Varint();
layer.Name = layerReader.GetString(strLength);
break;
case LayerType.Extent:
layer.Extent = (ulong)layerReader.Varint();
break;
case LayerType.Keys:
byte[] keyBuffer = layerReader.View();
string key = Encoding.UTF8.GetString(keyBuffer, 0, keyBuffer.Length);
layer.Keys.Add(key);
break;
case LayerType.Values:
byte[] valueBuffer = layerReader.View();
PbfReader valReader = new PbfReader(valueBuffer);
while (valReader.NextByte()) {
switch ((ValueType)valReader.Tag) {
case ValueType.String:
byte[] stringBuffer = valReader.View();
string value = Encoding.UTF8.GetString(stringBuffer, 0, stringBuffer.Length);
layer.Values.Add(value);
break;
case ValueType.Float:
float snglVal = valReader.GetFloat();
layer.Values.Add(snglVal);
break;
case ValueType.Double:
double dblVal = valReader.GetDouble();
layer.Values.Add(dblVal);
break;
case ValueType.Int:
long i64 = valReader.Varint();
layer.Values.Add(i64);
break;
case ValueType.UInt:
long u64 = valReader.Varint();
layer.Values.Add(u64);
break;
case ValueType.SInt:
long s64 = valReader.Varint();
layer.Values.Add(s64);
break;
case ValueType.Bool:
long b = valReader.Varint();
layer.Values.Add(b == 1);
break;
default:
throw new System.Exception(string.Format(
NumberFormatInfo.InvariantInfo
, "NOT IMPLEMENTED valueReader.Tag:{0} valueReader.WireType:{1}"
, valReader.Tag
, valReader.WireType
));
//uncomment the following lines when not throwing!!
//valReader.Skip();
//break;
}
}
break;
case LayerType.Features:
layer.AddFeatureData(layerReader.View());
break;
default:
layerReader.Skip();
break;
}
}
if (_Validate) {
if (string.IsNullOrEmpty(layer.Name)) {
throw new System.Exception("Layer has no name");
}
if (0 == layer.Version) {
throw new System.Exception(string.Format("Layer [{0}] has invalid version. Only version 2.x of 'Mapbox Vector Tile Specification' (https://github.com/mapbox/vector-tile-spec) is supported.", layer.Name));
}
if (2 != layer.Version) {
throw new System.Exception(string.Format("Layer [{0}] has invalid version: {1}. Only version 2.x of 'Mapbox Vector Tile Specification' (https://github.com/mapbox/vector-tile-spec) is supported.", layer.Name, layer.Version));
}
if (0 == layer.Extent) {
throw new System.Exception(string.Format("Layer [{0}] has no extent.", layer.Name));
}
if (0 == layer.FeatureCount()) {
throw new System.Exception(string.Format("Layer [{0}] has no features.", layer.Name));
}
//TODO: find equivalent of 'Distinct()' for NET20
#if !NET20
if (layer.Values.Count != layer.Values.Distinct().Count()) {
throw new System.Exception(string.Format("Layer [{0}]: duplicate attribute values found", layer.Name));
}
#endif
}
return layer;
}
/// <summary>
/// Get a feature of the <see cref="VectorTileLayer"/>
/// </summary>
/// <param name="layer"><see cref="VectorTileLayer"/> containing the feature</param>
/// <param name="data">Raw byte data of the feature</param>
/// <param name="validate">If true, run checks if the tile contains valid data. Decreases decoding speed.</param>
/// <param name="clippBuffer">
/// <para>'null': returns the geometries unaltered as they are in the vector tile. </para>
/// <para>Any value >=0 clips a border with the size around the tile. </para>
/// <para>These are not pixels but the same units as the 'extent' of the layer. </para>
/// </param>
/// <returns></returns>
public static VectorTileFeature GetFeature(
VectorTileLayer layer
, byte[] data
, bool validate = true
, uint? clippBuffer = null
) {
PbfReader featureReader = new PbfReader(data);
VectorTileFeature feat = new VectorTileFeature(layer);
bool geomTypeSet = false;
while (featureReader.NextByte()) {
int featureType = featureReader.Tag;
if (validate) {
if (!ConstantsAsDictionary.FeatureType.ContainsKey(featureType)) {
throw new System.Exception(string.Format("Layer [{0}] has unknown feature type: {1}", layer.Name, featureType));
}
}
switch ((FeatureType)featureType) {
case FeatureType.Id:
feat.Id = (ulong)featureReader.Varint();
break;
case FeatureType.Tags:
#if NET20
List<int> tags = featureReader.GetPackedUnit32().ConvertAll<int>(ui => (int)ui);
#else
List<int> tags = featureReader.GetPackedUnit32().Select(t => (int)t).ToList();
#endif
feat.Tags = tags;
break;
case FeatureType.Type:
int geomType = (int)featureReader.Varint();
if (validate) {
if (!ConstantsAsDictionary.GeomType.ContainsKey(geomType)) {
throw new System.Exception(string.Format("Layer [{0}] has unknown geometry type tag: {1}", layer.Name, geomType));
}
}
feat.GeometryType = (GeomType)geomType;
geomTypeSet = true;
break;
case FeatureType.Geometry:
if (null != feat.Geometry) {
throw new System.Exception(string.Format("Layer [{0}], feature already has a geometry", layer.Name));
}
//get raw array of commands and coordinates
List<uint> geometryCommands = featureReader.GetPackedUnit32();
//decode commands and coordinates
List<List<Point2d>> geom = DecodeGeometry.GetGeometry(
layer.Extent
, feat.GeometryType
, geometryCommands
);
if (clippBuffer.HasValue) {
geom = clipGeometries(geom, feat.GeometryType, (long)layer.Extent, clippBuffer.Value);
}
feat.Geometry = geom;
break;
default:
featureReader.Skip();
break;
}
}
if (validate) {
if (!geomTypeSet) {
throw new System.Exception(string.Format("Layer [{0}]: feature missing geometry type", layer.Name));
}
if (null == feat.Geometry) {
throw new System.Exception(string.Format("Layer [{0}]: feature has no geometry", layer.Name));
}
if (0 != feat.Tags.Count % 2) {
throw new System.Exception(string.Format("Layer [{0}]: uneven number of feature tag ids", layer.Name));
}
if (feat.Tags.Count > 0) {
#if NET20
int maxKeyIndex = -9999;
for(int i = 0; i < feat.Tags.Count; i += 2) {
if(feat.Tags[i] > maxKeyIndex) { maxKeyIndex = feat.Tags[i]; }
}
int maxValueIndex = -9999;
for(int i = 1; i < feat.Tags.Count; i += 2) {
if(feat.Tags[i] > maxValueIndex) { maxValueIndex = feat.Tags[i]; }
}
#else
int maxKeyIndex = feat.Tags.Where((key, idx) => idx % 2 == 0).Max();
int maxValueIndex = feat.Tags.Where((key, idx) => (idx + 1) % 2 == 0).Max();
#endif
if (maxKeyIndex >= layer.Keys.Count) {
throw new System.Exception(string.Format("Layer [{0}]: maximum key index equal or greater number of key elements", layer.Name));
}
if (maxValueIndex >= layer.Values.Count) {
throw new System.Exception(string.Format("Layer [{0}]: maximum value index equal or greater number of value elements", layer.Name));
}
}
}
return feat;
}
private static List<List<Point2d>> clipGeometries(
List<List<Point2d>> geoms
, GeomType geomType
, long extent
, uint bufferSize
) {
List<List<Point2d>> retVal = new List<List<Point2d>>();
//points: simply remove them if one part of the coordinate pair is out of bounds:
// <0 || >extent
if (geomType == GeomType.POINT) {
foreach (var geomPart in geoms) {
List<Point2d> outGeom = new List<Point2d>();
foreach (var geom in geomPart) {
if (
geom.X < (0L - bufferSize)
|| geom.Y < (0L - bufferSize)
|| geom.X > (extent + bufferSize)
|| geom.Y > (extent + bufferSize)
) {
continue;
}
outGeom.Add(geom);
}
if (outGeom.Count > 0) {
retVal.Add(outGeom);
}
}
return retVal;
}
//use clipper for lines and polygons
bool closed = true;
if (geomType == GeomType.LINESTRING) { closed = false; }
Polygons subjects = new Polygons();
Polygons clip = new Polygons(1);
Polygons solution = new Polygons();
clip.Add(new Polygon(4));
clip[0].Add(new InternalClipper.IntPoint(0L - bufferSize, 0L - bufferSize));
clip[0].Add(new InternalClipper.IntPoint(extent + bufferSize, 0L - bufferSize));
clip[0].Add(new InternalClipper.IntPoint(extent + bufferSize, extent + bufferSize));
clip[0].Add(new InternalClipper.IntPoint(0L - bufferSize, extent + bufferSize));
foreach (var geompart in geoms) {
Polygon part = new Polygon();
foreach (var geom in geompart) {
part.Add(new InternalClipper.IntPoint(geom.X, geom.Y));
}
subjects.Add(part);
}
InternalClipper.Clipper c = new InternalClipper.Clipper();
c.AddPaths(subjects, InternalClipper.PolyType.ptSubject, closed);
c.AddPaths(clip, InternalClipper.PolyType.ptClip, true);
bool succeeded = false;
if (geomType == GeomType.LINESTRING) {
InternalClipper.PolyTree lineSolution = new InternalClipper.PolyTree();
succeeded = c.Execute(
InternalClipper.ClipType.ctIntersection
, lineSolution
, InternalClipper.PolyFillType.pftNonZero
, InternalClipper.PolyFillType.pftNonZero
);
if (succeeded) {
solution = InternalClipper.Clipper.PolyTreeToPaths(lineSolution);
}
} else {
succeeded = c.Execute(
InternalClipper.ClipType.ctIntersection
, solution
, InternalClipper.PolyFillType.pftNonZero
, InternalClipper.PolyFillType.pftNonZero
);
}
if (succeeded) {
retVal = new List<List<Point2d>>();
foreach (var part in solution) {
List<Point2d> geompart = new List<Point2d>();
foreach (var geom in part) {
geompart.Add(new Point2d() { X = geom.X, Y = geom.Y });
}
retVal.Add(geompart);
}
return retVal;
} else {
//if clipper was not successfull return original geometries
return geoms;
}
}
}
}