Updated Vector Data Representation for WebGL (markdown)
@@ -123,4 +123,209 @@ For efficiency, a good implementation should have the following properties:
|
||||
|
||||
## Real life examples
|
||||
|
||||
These best practices are well demonstrated in Cesium's [PolylineCollection](https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/PolylineCollection.js) and [BillboardCollection](https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/BillboardCollection.js).
|
||||
These best practices are well demonstrated in Cesium's [PolylineCollection](https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/PolylineCollection.js) and [BillboardCollection](https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/BillboardCollection.js).
|
||||
|
||||
# Concrete implementation
|
||||
|
||||
## Objective
|
||||
|
||||
The objective of this section is to outline a vector data representation that will allow the implementation of efficient Canvas 2D and WebGL renderers. "Efficient" in this context means:
|
||||
|
||||
* coordinates do not need to be copied into new data structures, the data structures are usable directly by both Canvas 2D and WebGL
|
||||
* styling information does not need to be copied into data structures or re-calculated
|
||||
* when geometries are added, updated, or deleted, the changes to the data structures are minimised
|
||||
* when styles are updated, changes to the data structures are minimised
|
||||
|
||||
This document primarily focuses on geometries, although some suggestions as to how styling can be implemented will be given.
|
||||
|
||||
|
||||
## High level view
|
||||
|
||||
Canvas 2D is a sequential renderer: features are rendered one at a time, in sequence. WebGL rendering is parallel: multiple features are rendered simultaneously. Therefore, we store related geometries in "collections" that can be passed as a unit to the WebGL renderer, and trivially iterated over in the Canvas 2D renderer. In general, all geometries in a collection will be of the same type (e.g. point, linestring, polygon).
|
||||
|
||||
It remains to be decided how many collections are used. As an initial guideline, a vector source will likely have one collection per type of geometry present in the source.
|
||||
|
||||
A collection contains:
|
||||
|
||||
* Meta information about the type of geometry stored.
|
||||
* An `Array.<number>` containing the raw, unpacked coordinates (e.g. `[x1, y1, x2, y2, ...]`), called the vertex array, length 2N where N is the number of coordinates.
|
||||
* For linestring and polygon collections, an `Array.<number>` containing indexes 0..N-1 into the vertex array, whose length will vary according to the geometries present.
|
||||
* One or more `Array.<number>`s containing cached styling information (e.g. line width, point size, color components). For point collections, this will have as many elements as there are vertices. For linestring and polygon collections, this will have as many elements as the index array.
|
||||
* Further meta information to allow the extraction and modification of individual geometries in the collection.
|
||||
|
||||
|
||||
## Reading external data
|
||||
|
||||
Format parsers should create collections directly, or extend existing collections.
|
||||
|
||||
|
||||
## Rendering points
|
||||
|
||||
Point collections contain a vertex array of unpacked coordinates and zero or more styling arrays.
|
||||
|
||||
### Canvas 2D
|
||||
|
||||
The renderer loops over the vertex arrays, drawing points according to the contents of the styling array(s). Points are drawn in series. The styling arrays can be used to avoid unnecessary canvas state changes.
|
||||
|
||||
### WebGL
|
||||
|
||||
The renderer uploads the vertex arrays and styling array(s) to the GPU, and tells the GPU to draw the points in parallel.
|
||||
|
||||
|
||||
## Rendering linestrings
|
||||
|
||||
A linestring collection contains multiple linestrings. They contain a vertex array of unpacked coordinates, an index array for Canvas 2D, an index array for WebGL, and zero or more styling arrays. The WebGL-specific index array should be created only if needed. For example, consider the linestrings:
|
||||
|
||||
```
|
||||
A
|
||||
\ D----E
|
||||
\
|
||||
B---C
|
||||
```
|
||||
|
||||
In this case, the vertex array contains:
|
||||
|
||||
```
|
||||
[Ax, Ay, Bx, By, Cx, Cy, Dx, Dy, Ex, Ey]
|
||||
```
|
||||
|
||||
We populate the index array with successive pairs of start and stop indexes in the vertex array for each linestring. That is, it contains a linestring using vertices [0..3) (i.e. A, B, C) and a linestring using vertices [3, 5) (i.e. D, E):
|
||||
|
||||
```
|
||||
[0, 3, 3, 5]
|
||||
```
|
||||
|
||||
### Canvas 2D
|
||||
|
||||
The Canvas 2D renderer iterates over the pairs of indexes in the index array, drawing lines one by one. The style arrays can be used to avoid unnecessary state changes.
|
||||
|
||||
### WebGL
|
||||
|
||||
The following is subject to change as rendering styled lines in WebGL is non-trivial.
|
||||
|
||||
#### Approach 1: rendering simple lines with a single style
|
||||
|
||||
WebGL can draw simple line segments. Nominally these can be of any width, but practically only width 1 is widely supported. We compute an index array with pairs of line vertices. For example:
|
||||
|
||||
```
|
||||
[0, 1, 1, 2, 2, 3, 4, 5]
|
||||
```
|
||||
|
||||
The vertex array and index arrays are uploaded to the GPU, and the line segments are drawn in parallel.
|
||||
|
||||
#### Approach 2: rendering lines with two triangles
|
||||
|
||||
See [drawing nearly perfect 2D line segments in OpenGL](http://www.codeproject.com/Articles/199525/Drawing-nearly-perfect-2D-line-segments-in-OpenGL) for one promising technique. It may be possible to calculate the triangle vertices in the vertex shader, and therefore avoid having to calculate them in JavaScript.
|
||||
|
||||
|
||||
## Rendering polygons
|
||||
|
||||
A polygon collection contains multiple polygons. Once again we use a common vertex array, and renderer-specific index arrays. In this description, we do not consider polygons with holes, but this does not preclude the implementation of polygons with holes in the future.
|
||||
|
||||
Consider the polygons:
|
||||
|
||||
```
|
||||
A----B E
|
||||
| | / \
|
||||
| | / \
|
||||
D----C G-----F
|
||||
```
|
||||
|
||||
The vertex array contains the unpacked vertices:
|
||||
|
||||
```
|
||||
[Ax, Ay, Bx, By, Cx, Cy, Dx, Dy, Ex, Ey, Fx, Fy, Gx, Gy]
|
||||
```
|
||||
|
||||
During construction of polygon collection, we specify which vertices belong to which polygon:
|
||||
|
||||
```
|
||||
[[0, 1, 2, 3], [4, 5, 6]]
|
||||
```
|
||||
|
||||
These nested arrays can equivalently be represented as two arrays, the first containing the vertex indexes and the second containing successive pairs of ranges:
|
||||
|
||||
```
|
||||
[0, 1, 2, 3, 4, 5, 6]
|
||||
[0, 4, 4, 7]
|
||||
```
|
||||
|
||||
### Canvas 2D
|
||||
|
||||
The canvas 2D renderer loops over the polygons, drawing them one by one.
|
||||
|
||||
### WebGL
|
||||
|
||||
As described above, the WebGL renderer will need to split polygons into triangles. The polygons above would be split as:
|
||||
|
||||
```
|
||||
A----B E
|
||||
| \ | / \
|
||||
| \ | / \
|
||||
D----C G-----F
|
||||
```
|
||||
|
||||
We can re-use the existing vertex array and the triangulation step need only output a new index array in which successive triples of indexes represent triangles:
|
||||
|
||||
```
|
||||
[0, 1, 2, 0, 2, 3, 4, 5, 6]
|
||||
```
|
||||
|
||||
Note that a suitably advanced triangulation algorithm can cope with polygons with holes.
|
||||
|
||||
We upload the vertex array, the index array and the styling arrays to the GPU, and WebGL draws the triangles in parallel.
|
||||
|
||||
|
||||
## Updating geometries
|
||||
|
||||
Vertex, index and style arrays only need to re-uploaded to the GPU if they change. Re-scanning a geometry collection to see if anything has changed is clearly expensive and undesirable. Therefore, a suitable API is needed to efficiently flag when the array elements have changed.
|
||||
|
||||
|
||||
## Extracting the original geometries
|
||||
|
||||
It is clearly necessary to be able to extract the original geometries from the collection, e.g. to be able to return them to the server as GeoJSON objects. Necessary metadata needs to be stored in the collection to enable this. Renderers, however, should use the arrays directly for efficiency.
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
### Geometry editing
|
||||
|
||||
Typically a user will only edit one geometry at time, while the other geometries remain constant. To avoid re-drawing all features while a single geometry is being changed, the following technique should work well:
|
||||
|
||||
1. Temporarily disable the geometry in its collection.
|
||||
2. Add the geometry to a new collection (layer), containing only that geometry.
|
||||
3. While editing proceeds, only the collection (layer) containing the editable feature needs to be redrawn.
|
||||
4. When editing is complete, remove the temporary collection (layer) and re-insert the geometry in the original collection.
|
||||
|
||||
This also has the nice side effect of ensuring that the edited feature is always on top.
|
||||
|
||||
### Avoiding floating point errors in WebGL
|
||||
|
||||
WebGL internally uses 32-bit floating point values. These are insufficiently precise for geographical coordinates. A work around is to give each collection a local origin and make its coordinates relative to this local origin.
|
||||
|
||||
The same technique could be used in the canvas renderer, allowing 32-bit floats to be used for geographical coordinates and therefore halving memory usage.
|
||||
|
||||
### Garbage collection
|
||||
|
||||
Although this structure of arrays is more complex to manage, it places much less load on the garbage collector since there are far fewer objects created. Good JavaScript implementations will recognize that the arrays only contain number values, and therefore not traverse them during the mark and sweep phase. Contrast this with the GC impact of arrays of `ol.Coordinate`s.
|
||||
|
||||
### TopoJSON
|
||||
|
||||
[TopoJSON](https://github.com/mbostock/topojson/) uses shared vertices for significant speed and size improvements over GeoJSON. It is not possible to take advantage TopoJSON without using a shared coordinate array.
|
||||
|
||||
### Binary representation of vector data
|
||||
|
||||
As more and more vector features are added, the overhead of XML or JSON parsing can become significant. Using [ArrayBuffers](https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays/ArrayBuffer), it is possible to read vertex and index data directly into a typed array and therefore avoid all parsing overhead. This is one of the techniques used by [Here (Nokia) Maps 3D](http://here.com/50.0508706,-1.3870925,4,0,0,3d.day) to achieve such high performance.
|
||||
|
||||
### 3D terrain and models
|
||||
|
||||
3D models, e.g. Triangular Irregular Network elevation tiles and buildings, are typically generated on the server and served to the client as an array of vertices and an array of triangle indexes. With the above representation, the same vector representation can be used for 2D vector data, 3D models and terrain tiles.
|
||||
|
||||
|
||||
## Not covered in this document
|
||||
|
||||
* howto handle polygons with holes
|
||||
* how to structure metadata so that multipoints, multilinestrings and multipolygons can be extracted
|
||||
* how to draw non-trivial lines in WebGL
|
||||
* how to symbolize points with an image
|
||||
* how to implement hit detection
|
||||
Reference in New Issue
Block a user