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

iTowns / itowns / 10635241580

30 Aug 2024 03:26PM UTC coverage: 86.966% (-2.8%) from 89.766%
10635241580

push

github

jailln
feat(3dtiles): add new OGC3DTilesLayer using 3d-tiles-renderer-js

Deprecate C3DTilesLayer (replaced by OGC3DTilesLayer).
Add new iGLTFLoader that loads gltf 1.0 and 2.0 files.

2791 of 3694 branches covered (75.55%)

Branch coverage included in aggregate %.

480 of 644 new or added lines in 8 files covered. (74.53%)

2144 existing lines in 111 files now uncovered.

24319 of 27479 relevant lines covered (88.5%)

1024.72 hits per line

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

89.07
/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';
1✔
8

1✔
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

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

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

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

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

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

30✔
57
        length--;
30✔
58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
182
/**
1✔
183
 * @module VectorTileParser
1✔
184
 */
1✔
185
export default {
1✔
186
    /**
1✔
187
     * Parse a vector tile file and return a [Feature]{@link module:GeoJsonParser.Feature}
1✔
188
     * or an array of Features. While multiple formats of vector tile are
1✔
189
     * available, the only one supported for the moment is the
1✔
190
     * [Mapbox Vector Tile]{@link https://www.mapbox.com/vector-tiles/specification/}.
1✔
191
     *
1✔
192
     * @param {ArrayBuffer} file - The vector tile file to parse.
1✔
193
     *
1✔
194
     * @param {ParsingOptions} options - Options controlling the parsing {@link ParsingOptions}.
1✔
195
     *
1✔
196
     * @param {InformationsData} options.in - Object containing all styles,
1✔
197
     * layers and informations data, see {@link InformationsData}.
1✔
198
     *
1✔
199
     * @param {Object} options.in.styles - Object containing subobject with
1✔
200
     * informations on a specific style layer. Styles available is by `layer.id` and by zoom.
1✔
201
     *
1✔
202
     * @param {Object} options.in.layers - Object containing subobject with
1✔
203
     *
1✔
204
     * @param {FeatureBuildingOptions} options.out - options indicates how the features should be built,
1✔
205
     * see {@link FeatureBuildingOptions}.
1✔
206
     *
1✔
207
     * @return {Promise} A Promise resolving with a Feature or an array a
1✔
208
     * Features.
1✔
209
     */
1✔
210
    parse(file, options) {
1✔
211
        options = deprecatedParsingOptionsToNewOne(options);
5✔
212
        return Promise.resolve(readPBF(file, options));
5✔
213
    },
5✔
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