• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iTowns / itowns / 5937406206

22 Aug 2023 10:09AM UTC coverage: 76.393% (-0.05%) from 76.443%
5937406206

push

github

mgermerie
example(VectorTile): new example using official mapbox flux, showing buildings placed at ground level.

3934 of 5872 branches covered (67.0%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

7787 of 9471 relevant lines covered (82.22%)

1615.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.89
/src/Parser/VectorTileParser.js
1
import { Vector2, Vector3 } from 'three';
1✔
2
import Protobuf from 'pbf';
1✔
3
import { VectorTile } from '@mapbox/vector-tile';
1✔
4
import { globalExtentTMS } from 'Core/Geographic/Extent';
1✔
5
import { FeatureCollection, FEATURE_TYPES } from 'Core/Feature';
1✔
6
import { deprecatedParsingOptionsToNewOne } from 'Core/Deprecated/Undeprecator';
1✔
7
import Coordinates from 'Core/Geographic/Coordinates';
15!
8

9
const worldDimension3857 = globalExtentTMS.get('EPSG:3857').planarDimensions();
1✔
10
const globalExtent = new Vector3(worldDimension3857.x, worldDimension3857.y, 1);
1✔
11
const lastPoint = new Vector2();
1✔
12
const firstPoint = new Vector2();
1✔
13

14
// Calculate the projected coordinates in EPSG:4326 of a given point in the VT local system
15
// adapted from @mapbox/vector-tile
16
function project(x, y, tileNumbers, tileExtent) {
17
    const size = tileExtent * 2 ** tileNumbers.z;
8✔
18
    const x0 = tileExtent * tileNumbers.x;
8✔
19
    const y0 = tileExtent * tileNumbers.y;
8✔
20
    const y2 = 180 - (y + y0) * 360 / size;
21
    return new Coordinates(
8✔
22
        'EPSG:4326',
23
        (x + x0) * 360 / size - 180,
24
        360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90,
25
    );
26
}
27

28
// Classify option, it allows to classify a full polygon and its holes.
29
// Each polygon with its holes are in one FeatureGeometry.
30
// A polygon is determined by its clockwise direction and the holes are in the opposite direction.
31
// Clockwise direction is determined by Shoelace formula https://en.wikipedia.org/wiki/Shoelace_formula
32
// Draw polygon with canvas doesn't need to classify however it is necessary for meshs.
33
function vtFeatureToFeatureGeometry(vtFeature, feature, classify = false) {
1!
34
    let geometry = feature.bindNewGeometry();
1✔
35
    const isPolygon = feature.type === FEATURE_TYPES.POLYGON;
1✔
36
    classify = classify && isPolygon;
1!
37

38
    geometry.properties = vtFeature.properties;
1✔
39
    const pbf = vtFeature._pbf;
1✔
40
    pbf.pos = vtFeature._geometry;
1✔
41

42
    const end = pbf.readVarint() + pbf.pos;
1✔
43
    let cmd = 1;
1✔
44
    let length = 0;
1✔
45
    let x = 0;
1✔
46
    let y = 0;
1✔
47
    let count = 0;
1✔
48
    let sum = 0;
1✔
49

50
    while (pbf.pos < end) {
1✔
51
        if (length <= 0) {
10✔
52
            const cmdLen = pbf.readVarint();
6✔
53
            cmd = cmdLen & 0x7;
6✔
54
            length = cmdLen >> 3;
6✔
55
        }
56

57
        length--;
10✔
58

59
        if (cmd === 1 || cmd === 2) {
10✔
60
            x += pbf.readSVarint();
8✔
61
            y += pbf.readSVarint();
8✔
62

63
            if (cmd === 1) {
8✔
64
                if (count) {
2✔
65
                    if (classify && sum > 0 && geometry.indices.length > 0) {
1!
66
                        feature.updateExtent(geometry);
×
67
                        geometry = feature.bindNewGeometry();
×
68
                        geometry.properties = vtFeature.properties;
×
69
                    }
70
                    geometry.closeSubGeometry(count, feature);
1✔
71
                    geometry.getLastSubGeometry().ccw = sum < 0;
1✔
72
                }
73
                count = 0;
2✔
74
                sum = 0;
2✔
75
            }
76
            count++;
8✔
77
            const coordProj = project(
8✔
78
                x,
79
                y,
80
                vtFeature.tileNumbers,
81
                vtFeature.extent);
82
            geometry.pushCoordinatesValues(feature, { x, y }, coordProj);
8✔
83
            if (count == 1) {
8✔
84
                firstPoint.set(x, y);
2✔
85
                firstPoint.coordProj = coordProj;
2✔
86
                lastPoint.set(x, y);
2✔
87
            } else if (isPolygon && count > 1) {
6!
88
                sum += (lastPoint.x - x) * (lastPoint.y + y);
6✔
89
                lastPoint.set(x, y);
6✔
90
            }
91
        } else if (cmd === 7) {
2!
92
            if (count) {
2!
93
                count++;
2✔
94
                geometry.pushCoordinatesValues(feature, { x: firstPoint.x, y: firstPoint.y }, firstPoint.coordProj);
2✔
95
                if (isPolygon) {
2!
96
                    sum += (lastPoint.x - firstPoint.x) * (lastPoint.y + firstPoint.y);
2✔
97
                }
98
            }
99
        } else {
100
            throw new Error(`unknown command ${cmd}`);
×
101
        }
102
    }
103

104
    if (count) {
1!
105
        if (classify && sum > 0 && geometry.indices.length > 0) {
1!
106
            feature.updateExtent(geometry);
×
107
            geometry = feature.bindNewGeometry();
×
108
            geometry.properties = vtFeature.properties;
×
109
        }
110
        geometry.closeSubGeometry(count, feature);
1✔
111
        geometry.getLastSubGeometry().ccw = sum < 0;
1✔
112
    }
113
    feature.updateExtent(geometry);
1✔
114
}
115

116
function readPBF(file, options) {
117
    options.out = options.out || {};
3!
118
    const vectorTile = new VectorTile(new Protobuf(file));
3✔
119
    const sourceLayers = Object.keys(vectorTile.layers);
3✔
120

121
    if (sourceLayers.length < 1) {
3✔
122
        return;
1✔
123
    }
124

125
    // x,y,z tile coordinates
126
    const x = file.extent.col;
2✔
127
    const z = file.extent.zoom;
2✔
128
    // We need to move from TMS to Google/Bing/OSM coordinates
129
    // https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/
130
    // Only if the layer.origin is top
131
    const y = options.in.isInverted ? file.extent.row : (1 << z) - file.extent.row - 1;
2!
132

133
    const collection = new FeatureCollection(options.out);
2✔
134

135
    const vFeature = vectorTile.layers[sourceLayers[0]];
2✔
136
    // TODO: verify if size is correct because is computed with only one feature (vFeature).
137
    const size = vFeature.extent * 2 ** z;
2✔
138
    const center = -0.5 * size;
2✔
139

140
    collection.scale.set(globalExtent.x / size, -globalExtent.y / size, 1);
2✔
141
    collection.position.set(vFeature.extent * x + center, vFeature.extent * y + center, 0).multiply(collection.scale);
2✔
142
    collection.updateMatrixWorld();
2✔
143

144
    sourceLayers.forEach((layer_id) => {
2✔
145
        if (!options.in.layers[layer_id]) { return; }
2✔
146

147
        const sourceLayer = vectorTile.layers[layer_id];
1✔
148

149
        for (let i = sourceLayer.length - 1; i >= 0; i--) {
1✔
150
            const vtFeature = sourceLayer.feature(i);
1✔
151
            vtFeature.tileNumbers = { x, y: file.extent.row, z };
1✔
152
            const layers = options.in.layers[layer_id].filter(l => l.filterExpression.filter({ zoom: z }, vtFeature) && z >= l.zoom.min && z < l.zoom.max);
1✔
153
            let feature;
1✔
154

155
            for (const layer of layers) {
2✔
156
                if (!feature) {
1!
157
                    feature = collection.requestFeatureById(layer.id, vtFeature.type - 1);
1✔
158
                    feature.id = layer.id;
1✔
159
                    feature.order = layer.order;
1✔
160
                    feature.style = options.in.styles[feature.id];
1✔
161
                    vtFeatureToFeatureGeometry(vtFeature, feature);
1✔
162
                } else if (!collection.features.find(f => f.id === layer.id)) {
×
163
                    feature = collection.newFeatureByReference(feature);
×
164
                    feature.id = layer.id;
×
165
                    feature.order = layer.order;
×
166
                    feature.style = options.in.styles[feature.id];
×
167
                }
168
            }
1✔
169
        }
170
    });
171

172
    collection.removeEmptyFeature();
2✔
173
    // TODO Some vector tiles are already sorted
174
    collection.features.sort((a, b) => a.order - b.order);
2✔
175
    // TODO verify if is needed to updateExtent for previous features.
176
    collection.updateExtent();
2✔
177
    collection.extent = file.extent;
2✔
178
    collection.isInverted = options.in.isInverted;
2✔
179
    return Promise.resolve(collection);
2✔
180
}
181

182
/**
183
 * @module VectorTileParser
184
 */
185
export default {
1✔
186
    /**
187
     * Parse a vector tile file and return a [Feature]{@link module:GeoJsonParser.Feature}
188
     * or an array of Features. While multiple formats of vector tile are
189
     * available, the only one supported for the moment is the
190
     * [Mapbox Vector Tile]{@link https://www.mapbox.com/vector-tiles/specification/}.
191
     *
192
     * @param {ArrayBuffer} file - The vector tile file to parse.
193
     *
194
     * @param {ParsingOptions} options - Options controlling the parsing {@link ParsingOptions}.
195
     *
196
     * @param {InformationsData} options.in - Object containing all styles,
197
     * layers and informations data, see {@link InformationsData}.
198
     *
199
     * @param {Object} options.in.styles - Object containing subobject with
200
     * informations on a specific style layer. Styles available is by `layer.id` and by zoom.
201
     *
202
     * @param {Object} options.in.layers - Object containing subobject with
203
     *
204
     * @param {FeatureBuildingOptions} options.out - options indicates how the features should be built,
205
     * see {@link FeatureBuildingOptions}.
206
     *
207
     * @return {Promise} A Promise resolving with a Feature or an array a
208
     * Features.
209
     */
210
    parse(file, options) {
211
        options = deprecatedParsingOptionsToNewOne(options);
3✔
212
        return Promise.resolve(readPBF(file, options));
3✔
213
    },
214
};
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc