Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.0.2 draft #36

Closed
wants to merge 11 commits into from
91 changes: 91 additions & 0 deletions 1.0.2-draft/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# vector-tile-spec

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in
this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).

## 1. Purpose

This specification attempts to create a standard for encoding of tiled geospatial data that can be shared across clients.

## 2. File format

The vector tile encoding scheme encodes vector data for a tile in a space efficient manner. It is designed to be used in browsers or serverside applications for fast rendering or lookups of feature data.

Vector Tiles use [Google Protocol buffers](https://developers.google.com/protocol-buffers/) as a container format. It is exclusively geared towards square pixel tiles in [Spherical Mercator projection](http://wiki.openstreetmap.org/wiki/Mercator).

## 3. Internal structure

A vector tile SHOULD consist of one or more named layers.

A layer MUST contain a field describing its name and a version of the vector tile spec from which it was created. A layer MUST also contain one or more features. A layer MAY contain one or more sets of feature attributes.

Features MAY contain an `id` and `tags` (attributes) and MUST contain either a `geometry` (either point, linestring, or polygon) or a `raster` field. A feature SHALL NOT contain both a `raster` and a `geometry` field. If a feature has a `geometry` field it MUST also have a `type` field to describe the `geometry`.

### 3.1. Geometry Encoding

Geometries are stored as an a single array of integers that represent a command,x,y stream (where command is a rendering command like `move_to` or `line_to`). Commands are encoded only when they change.

Geometries with multiple parts (multipoint, multiline, or multipolygon) MUST be encoded one after another in the same `geometry` field and therefore are "flattened". Geometries with only a single part MUST have only a single `move_to` present. For multipoints and multilines a repeated `move_to` SHALL indicate another part of a multipart geometry. For polygons a repeated `move_to` SHALL indicate either another exterior of a new polygon part or an interior ring of the previous polygon part. The winding order MUST be used to distinguish between the two types: polygon interior rings (holes) must be oriented with the opposite winding order than their parent exterior rings and all interior rings MUST directly follow the exterior ring to which they belong.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the last in a ring point be the same as the first point, or are polygon rings implicitly closed? Section 6.1.7.1 of the "Simple" Features spec says that linear rings must be both simple and closed explicitly (first point = last point), but I don't think that gains us anything. Instead, could this spec say something like "exterior and interior rings MAY be closed. If they are not, then client software SHOULD treat them as implicitly closed (e.g: by appending the first point)."?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zerebubuth I will address this in the next commit to the spec. Thank you for bring it up.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever the spec resolution ends up being, my understanding is that Mapbox's JS decoder currently assumes that rings are implicitly closed, as it looks like it duplicates the first point when decoding (without checking if these are already the same): https://github.com/mapbox/vector-tile-js/blob/master/lib/vectortilefeature.js#L77

Is that a correct reading?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and implicit close will be mandatory in the next spec revision.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool thanks, that's been my expectation and preference, keeps things simpler.


The exterior ring MUST have a positive area as calculated by applying the [surveyor's formula](https://en.wikipedia.org/wiki/Shoelace_formula) to the vertices of the polygon in tile coordinates. In screen coordinates (with the Y axis positive down) this makes the exterior ring's winding order appear clockwise. In a frame where the Y axis is positive up, this would make the winding order appear counter clockwise.

Geometry collections are not supported.

Geometries SHOULD be clipped, reprojected into spherical mercator, converted to screen coordinates, and MUST be [delta](http://en.wikipedia.org/wiki/Delta_encoding) and [zigzag](https://developers.google.com/protocol-buffers/docs/encoding#types) encoded.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHOULD be clipped, reprojected into spherical mercator

Geometries are stored as graphics in the tiles, as they are projected, clipped and converted to screen coordinates.
The client doesn't have to know about what projection the geometries were before that process, as it consumes only the final screen coordinates.
I wouldn't word this as SHOULD IMO


Geometries SHOULD be a geometric objects that has no anomalous geometric points, such as self intersection or self tangency.

### 3.2. Feature Attributes

Feature attributes are encoded as key:value pairs which are dictionary encoded at the layer level for compact storage of any repeated keys or values. Values use [variant](https://developers.google.com/protocol-buffers/docs/encoding#varints) type encoding supporting both unicode strings, boolean values, and various integer and floating point types.

### 3.3. Example

For example, a GeoJSON feature like:

```json
{
"type": "FeatureCollection",
"features": [
{
"geometry": {
"type": "Point",
"coordinates": [
-8247861.1000836585,
4970241.327215323
]
},
"type": "Feature",
"properties": {
"hello": "world"
}
}
]
}
```

Would be structured like:

```js
layers {
name: "points"
features {
id: 1
tags: 0
tags: 0
type: Point
geometry: 9
geometry: 2410
geometry: 3080
}
keys: "hello"
values {
string_value: "world"
}
extent: 4096
version: 2
}
```

The authoritative details on encoding are part of the code comments for the [vector tile protobuf schema document](vector_tile.proto).
102 changes: 102 additions & 0 deletions 1.0.2-draft/vector_tile.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Protocol Version 1

package vector_tile;

option optimize_for = LITE_RUNTIME;

message Tile {
enum GeomType {
UNKNOWN = 0;
POINT = 1;
LINESTRING = 2;
POLYGON = 3;
}

// Variant type encoding
message Value {
// Exactly one of these values may be present in a valid message
optional string string_value = 1;
optional float float_value = 2;
optional double double_value = 3;
optional int64 int_value = 4;
optional uint64 uint_value = 5;
optional sint64 sint_value = 6;
optional bool bool_value = 7;

extensions 8 to max;
}

message Feature {
optional uint64 id = 1 [ default = 0 ];

// Tags of this feature are encoded as repeated pairs of
// integers. Even indexed values (n, beginning with 0) are
// themselves indexes into the layer's keys list. Odd indexed
// values (n+1) are indexes into the layer's values list.
// The first (n=0) tag of a feature, therefore, has a key of
// layer.keys[feature.tags[0]] and a value of
// layer.values[feature.tags[1]].
repeated uint32 tags = 2 [ packed = true ];

// The type of geometry stored in this feature.
optional GeomType type = 3 [ default = UNKNOWN ];

// Contains a stream of commands and parameters (vertices). The
// repeat count is shifted to the left by 3 bits. This means
// that the command has 3 bits (0-7). The repeat count
// indicates how often this command is to be repeated. Defined
// commands are:
// - MoveTo: 1 (2 parameters follow)
// - LineTo: 2 (2 parameters follow)
// - ClosePath: 7 (no parameters follow)
//
// Commands are encoded as uint32 varints. Vertex parameters
// are encoded as deltas to the previous position and, as they
// may be negative, are further "zigzag" encoded as unsigned
// 32-bit ints:
//
// n = (n << 1) ^ (n >> 31)
//
// Ex.: MoveTo(3, 6), LineTo(8, 12), LineTo(20, 34), ClosePath
// Encoded as: [ 9 6 12 18 10 12 24 44 15 ]
// | | `> [00001 111] command type 7 (ClosePath), length 1
// | | ===== relative LineTo(+12, +22) == LineTo(20, 34)
// | | ===== relative LineTo(+5, +6) == LineTo(8, 12)
// | `> [00010 010] = command type 2 (LineTo), length 2
// | ==== relative MoveTo(+3, +6)
// `> [00001 001] = command type 1 (MoveTo), length 1
//
// The original position is (0,0).
repeated uint32 geometry = 4 [ packed = true ];

optional bytes raster = 5;
}

message Layer {
// Any compliant implementation must first read the version
// number encoded in this message and choose the correct
// implementation for this version number before proceeding to
// decode other parts of this message.
required uint32 version = 15 [ default = 1 ];

required string name = 1;

// The actual features in this tile.
repeated Feature features = 2;

// Dictionary encoding for keys
repeated string keys = 3;

// Dictionary encoding for values
repeated Value values = 4;

// The bounding box in this tile spans from 0..4095 units
optional uint32 extent = 5 [ default = 4096 ];

extensions 16 to max;
}

repeated Layer layers = 3;

extensions 16 to 8191;
}
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Vector Tile Spec Changelog

### 1.0.2-draft

- Clarification of how polygon exterior and interior rings should be oriented and ordered: If present, any polygon interior rings (holes) must be oriented with the opposite winding order than their parent exterior rings and all interior rings must directly follow the exterior ring they belong too. Exterior rings must be oriented CCW and interior rings must be oriented CW (when viewed from the "top").
- Added optional `raster` field on feature intended to store encoded image data representing exact and unbuffered tile extents for a layer.

### 1.0.1

- Used TitleCase and UPPERCASE in `vector_tile.proto` to match Protobuf style guide (#3)
Expand Down