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

Beakerboy / OSMBuilding / 9102416920

15 May 2024 08:42PM UTC coverage: 40.556% (+0.4%) from 40.112%
9102416920

push

github

web-flow
Update building.js

41 of 66 branches covered (62.12%)

Branch coverage included in aggregate %.

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

14 existing lines in 2 files now uncovered.

616 of 1554 relevant lines covered (39.64%)

1.19 hits per line

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

80.1
/src/extras/BuildingShapeUtils.js
1
import {
3✔
2
  Shape,
3✔
3
  ShapeUtils,
3✔
4
} from 'three';
3✔
5

3✔
6
class BuildingShapeUtils extends ShapeUtils {
3✔
7

3✔
8
  /**
3✔
9
   * Create the shape of this way.
3✔
10
   *
3✔
11
   * @param {DOM.Element} way - OSM XML way element.
3✔
12
   * @param {[number, number]} nodelist - list of all nodes
3✔
13
   *
3✔
14
   * @return {THREE.Shape} shape - the shape
3✔
15
   */
3✔
16
  static createShape(way, nodelist) {
3✔
17
    // Initialize objects
3✔
18
    const shape = new Shape();
3✔
19
    var ref;
3✔
20
    var node = [];
3✔
21

3✔
22
    // Get all the nodes in the way of interest
3✔
23
    const elements = way.getElementsByTagName('nd');
3✔
24

3✔
25
    // Get the coordinates of all the nodes and add them to the shape outline.
3✔
26
    for (let i = 0; i < elements.length; i++) {
3✔
27
      ref = elements[i].getAttribute('ref');
14✔
28
      node = nodelist[ref];
14✔
29
      // The first node requires a differnet function call.
14✔
30
      if (i === 0) {
14✔
31
        shape.moveTo(parseFloat(node[0]), parseFloat(node[1]));
3✔
32
      } else {
11✔
33
        shape.lineTo(parseFloat(node[0]), parseFloat(node[1]));
11✔
34
      }
3✔
35
    }
3✔
36
    return shape;
3✔
37
  }
3✔
38

3✔
39
  /**
3✔
40
   * Check if a way is a cloased shape.
3✔
41
   *
3✔
42
   * @param {DOM.Element} way - OSM XML way element.
3✔
43
   *
3✔
44
   * @return {boolean}
3✔
45
   */
3✔
46
  static isClosed(way) {
3✔
47
    // Get all the nodes in the way of interest
11✔
48
    const elements = way.getElementsByTagName('nd');
11✔
49
    return elements[0].getAttribute('ref') === elements[elements.length - 1].getAttribute('ref');
3✔
50
  }
3✔
51

3✔
52
  /**
3✔
53
   * Walk through an array and seperate any closed ways.
3✔
54
   * Attempt to find matching open ways to enclose them.
3✔
55
   *
3✔
56
   * @param {[DOM.Element]} array - list of OSM XML way elements.
3✔
57
   *
3✔
58
   * @return {DOM.Element}
3✔
59
   */
3✔
60
  static combineWays(ways) {
3✔
61
    var closedWays = [];
3✔
62
    var openWays = [];
3✔
63
    var changed = true;
3✔
64
    while (changed) {
3✔
65
      changed = false;
5✔
66
      for (let i = 0; i < ways.length - 1; i++) {
5✔
67
        if (BuildingShapeUtils.isClosed(ways[i])) {
4✔
68
          closedWays.push(ways[i]);
2✔
69
        } else {
2✔
70
          const way1 = ways[i].getElementsByTagName('nd');
2✔
71
          const way2 = ways[i + 1].getElementsByTagName('nd');
2✔
72
          if (way2[0].getAttribute('ref') === way1[way1.length - 1].getAttribute('ref')) {
2✔
73
            const result = BuildingShapeUtils.joinWays(ways[i], ways[i + 1]);
2✔
74
            openWays.push(result);
2✔
75
            i++;
2✔
76
            changed = true;
2✔
77
          } else if (way1[0].getAttribute('ref') === way2[way2.length - 1].getAttribute('ref')) {
2!
78
            const result = BuildingShapeUtils.joinWays(ways[i + 1], ways[i]);
×
79
            openWays.push(result);
×
80
            i++;
×
81
            changed = true;
×
82
          } else {
×
83
            openWays.push(way1);
×
84
          }
2✔
85
        }
4✔
86
      }
5✔
87
      const lastWay = ways[ways.length - 1];
5✔
88
      if (BuildingShapeUtils.isClosed(lastWay)) {
5✔
89
        closedWays.push(lastWay);
1✔
90
      } else {
4✔
91
        openWays.push(lastWay);
4✔
92
      }
5✔
93
      ways = openWays;
5✔
94
      openWays = [];
3✔
95
    }
3✔
96
    return closedWays;
3✔
97
  }
3✔
98

3✔
99
  /**
3✔
100
   * Append the nodes from one way into another.
3✔
101
   *
3✔
102
   * @param {DOM.Element} way1 - an open, non self-intersecring way
3✔
103
   * @param {DOM.Element} way2
3✔
104
   *
3✔
105
   * @return {DOM.Element} way
3✔
106
   */
3✔
107
  static joinWays(way1, way2) {
3✔
108
    const elements = way2.getElementsByTagName('nd');
3✔
109
    for (let i = 1; i < elements.length; i++) {
3✔
110
      let elem = elements[i].cloneNode();
6✔
111
      way1.appendChild(elem);
3✔
112
    }
3✔
113
    return way1;
3✔
114
  }
3✔
115

3✔
116
  /**
3✔
117
   * Find the center of a closed way
3✔
118
   *
3✔
119
   * @param {THREE.Shape} shape - the shape
3✔
120
   *
3✔
121
   * @return {[number, number]} xy - x/y coordinates of the center
3✔
122
   */
3✔
123
  static center(shape) {
3✔
124
    const extents = BuildingShapeUtils.extents(shape);
×
125
    const center = [(extents[0] + extents[2] ) / 2, (extents[1]  + extents[3] ) / 2];
×
126
    return center;
3✔
127
  }
3✔
128

3✔
129
  /**
3✔
130
   * Return the longest cardinal side length.
3✔
131
   *
3✔
132
   * @param {THREE.Shape} shape - the shape
3✔
133
   */
3✔
134
  static getWidth(shape) {
×
135
    const xy = BuildingShapeUtils.combineCoordinates(shape);
×
136
    const x = xy[0];
×
137
    const y = xy[1];
×
138
    return Math.max(Math.max(...x) - Math.min(...x), Math.max(...y) - Math.min(...y));
3✔
139
  }
3✔
140

3✔
141
  /**
3✔
142
   * can points be an array of shapes?
3✔
143
   */
3✔
144
  static combineCoordinates(shape) {
×
145
    //console.log('Shape: ' + JSON.stringify(shape));
×
146
    const points = shape.extractPoints().shape;
×
147
    var x = [];
×
148
    var y = [];
×
149
    var vec;
×
150
    for (let i = 0; i < points.length; i++) {
×
151
      vec = points[i];
×
152
      x.push(vec.x);
×
153
      y.push(vec.y);
×
154
    }
×
155
    return [x, y];
3✔
156
  }
3✔
157

3✔
158
  /**
3✔
159
   * Calculate the Cartesian extents of the shape after rotaing couterclockwise by a given angle.
3✔
160
   *
3✔
161
   * @param {THREE.Shape} pts - the shape or Array of shapes.
3✔
162
   * @param {number} angle - angle in radians to rotate shape
3✔
163
   *
3✔
164
   * @return {[number, number, number, number]} the extents of the object.
3✔
165
   */
3✔
166
  static extents(shape, angle = 0) {
5✔
167
    if (!Array.isArray(shape)) {
5✔
168
      shape = [shape];
5✔
169
    }
5✔
170
    var x = [];
5✔
171
    var y = [];
5✔
172
    var vec;
5✔
173
    for (let i = 0; i < shape.length; i++) {
5✔
174
      const points = shape[i].extractPoints().shape;
5✔
175
      for (let i = 0; i < points.length; i++) {
5✔
176
        vec = points[i];
19✔
177
        x.push(vec.x * Math.cos(angle) - vec.y * Math.sin(angle));
19✔
178
        y.push(vec.x * Math.sin(angle) + vec.y * Math.cos(angle));
5✔
179
      }
5✔
180
    }
5✔
181
    const left = Math.min(...x);
5✔
182
    const bottom = Math.min(...y);
5✔
183
    const right = Math.max(...x);
5✔
184
    const top = Math.max(...y);
5✔
185
    return [left, bottom, right, top];
3✔
186
  }
3✔
187

3✔
188
  /**
3✔
189
   * Assuming the shape is all right angles,
3✔
190
   * Find the orientation of the longest edge.
3✔
191
   */
3✔
192
  static primaryDirection(shape) {
×
193
    const points = shape.extractPoints().shape;
3✔
194
  }
3✔
195

3✔
196
  /**
3✔
197
   * Calculate the length of each of a shape's edge
3✔
198
   *
3✔
199
   * @param {THREE.Shape} shape - the shape
3✔
200
   *
3✔
201
   * @return {[number, ...]} the esge lwngths.
3✔
202
   */
3✔
203
  static edgeLength(shape) {
2✔
204
    const points = shape.extractPoints().shape;
2✔
205
    const lengths = [];
2✔
206
    var p1;
2✔
207
    var p2;
2✔
208
    for (let i = 0; i < points.length - 1; i++) {
2✔
209
      p1 = points[i];
4✔
210
      p2 = points[i + 1];
4✔
211
      lengths.push(Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2));
2✔
212
    }
2✔
213
    p1 = points[points.length - 1];
2✔
214
    p2 = points[0];
2✔
215
    lengths.push(Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2));
2✔
216
    return lengths;
3✔
217
  }
3✔
218

3✔
219
  /**
3✔
220
   * Calculate the angle at each of a shape's vertex
3✔
221
   */
3✔
222
  static vertexAngle(shape) {
×
223
    const points = shape.extractPoints().shape;
×
224
    const angles = [];
×
225
    var p0;
×
226
    var p1;
×
227
    var p2;
×
228
    p0 = points[points.length];
×
229
    p1 = points[0];
×
230
    p2 = points[1];
×
231
    angles.push(Math.atan((p2.y - p1.y) / (p2.x - p1.x)) - Math.atan((p0.y - p1.y) / (p0.x - p1.x)));
×
232
    for (let i = 1; i < points.length - 1; i++) {
×
233
      p0 = points[i-1];
×
234
      p1 = points[i];
×
235
      p2 = points[i + 1];
×
236
      angles.push(Math.atan((p2.y - p1.y) / (p2.x - p1.x)) - Math.atan((p0.y - p1.y) / (p0.x - p1.x)));
×
237
    }
×
238
    p0 = points[points.length-1];
×
239
    p1 = points[points.length];
×
240
    p2 = points[0];
×
241
    angles.push(Math.atan((p2.y - p1.y) / (p2.x - p1.x)) - Math.atan((p0.y - p1.y) / (p0.x - p1.x)));
×
242
    return angles;
3✔
243
  }
3✔
244

3✔
245
  /**
3✔
246
   * Calculate the angle of each of a shape's edge.
3✔
247
   * the angle will be PI > x >= -PI
3✔
248
   *
3✔
249
   * @param {THREE.Shape} shape - the shape
3✔
250
   *
3✔
251
   * @return {[number, ...]} the angles in radians.
3✔
252
   */
3✔
253
  static edgeDirection(shape) {
3✔
254
    const points = shape.extractPoints().shape;
2✔
255
    points.push(points[0]);
2✔
256
    const angles = [];
2✔
257
    var p1;
2✔
258
    var p2;
2✔
259
    for (let i = 0; i < points.length - 1; i++) {
2✔
260
      p1 = points[i];
6✔
261
      p2 = points[i + 1];
6✔
262
      let angle = Math.atan2((p2.y - p1.y), (p2.x - p1.x));
6✔
263
      if (angle >= Math.PI / 2) {
6✔
264
        angle -= Math.PI;
6✔
265
      } else if (angle < -Math.PI / 2) {
4!
266
        angle += Math.PI;
6✔
267
      }
6✔
268
      angles.push(angle);
2✔
269
    }
2✔
270
    return angles;
3✔
271
  }
3✔
272

3✔
273
  /**
3✔
274
   * Count the number of times that a line horizontal from point intersects shape
3✔
275
   *
3✔
276
   * if an odd number are crossed, it is inside.
3✔
277
   * todo, test holes
3✔
278
   * Test edge conditions.
3✔
279
   */
3✔
280
  static surrounds(shape, point) {
×
281
    var count = 0;
×
282
    const vecs = shape.extractPoints().shape;
×
283
    var vec;
×
284
    var nextvec;
×
285
    for (let i = 0; i < vecs.length - 1; i++) {
×
286
      vec = vecs[i];
×
287
      nextvec = vecs[i+1];
×
288
      if (vec.x === point[0] && vec.y === point[1]) {
×
289
        return true;
×
290
      }
×
291
      if ((vec.x >= point[0] || nextvec.x >= point[0]) && (vec.y >= point[1] !== nextvec.y >= point[1])) {
×
292
        count++;
×
293
      }
×
294
    }
×
295
    return count % 2 === 1;
3✔
296
  }
3✔
297

3✔
298
  /**
3✔
299
   * Calculate the radius of a circle that can fit within a shape.
3✔
300
   *
3✔
301
   * @param {THREE.Shape} shape - the shape
3✔
302
   */
3✔
303
  static calculateRadius(shape) {
×
304
    const extents = BuildingShapeUtils.extents(shape);
×
305
    // return half of the shorter side-length.
×
306
    return Math.min(extents[2] - extents[0], extents[3] - extents[1]) / 2;
3✔
307
  }
3✔
308

3✔
309
  /**
3✔
310
   * Calculate the angle of the longest side of a shape with 90° vertices.
3✔
311
   * is begining / end duplicated?
3✔
312
   *
3✔
313
   * @param {THREE.Shape} shape - the shape
3✔
314
   * @return {number}
3✔
315
   */
3✔
316
  static longestSideAngle(shape) {
1✔
317
    const vecs = shape.extractPoints().shape;
1✔
318
    const lengths = BuildingShapeUtils.edgeLength(shape);
1✔
319
    const directions = BuildingShapeUtils.edgeDirection(shape);
1✔
320
    var index;
1✔
321
    var maxLength = 0;
1✔
322
    for (let i = 0; i < lengths.length; i++) {
1✔
323
      if (lengths[i] > maxLength) {
3✔
324
        index = i;
2✔
325
        maxLength = lengths[i];
3✔
326
      }
1✔
327
    }
1✔
328
    var angle = directions[index];
1✔
329
    const extents = BuildingShapeUtils.extents(shape, -angle);
1✔
330
    // If the shape is taller than it is wide after rotation, we are off by 90 degrees.
1✔
331
    if ((extents[3] - extents[1]) > (extents[2] - extents[0])) {
1!
332
      angle = angle > 0 ? angle - Math.PI / 2 : angle + Math.PI / 2;
1✔
333
    }
1✔
334
    return angle;
3✔
335
  }
3✔
336

3✔
337
  /**
3✔
338
   * Rotate lat/lon to reposition the home point onto 0,0.
3✔
339
   *
3✔
340
   * @param {[number, number]} lonLat - The longitute and latitude of a point.
3✔
341
   *
3✔
342
   * @return {[number, number]} x, y in meters
3✔
343
   */
3✔
UNCOV
344
  static repositionPoint(lonLat, home) {
×
UNCOV
345
    const R = 6371 * 1000;   // Earth radius in m
×
UNCOV
346
    const circ = 2 * Math.PI * R;  // Circumference
×
UNCOV
347
    const phi = 90 - lonLat[1];
×
UNCOV
348
    const theta = lonLat[0] - home[0];
×
UNCOV
349
    const thetaPrime = home[1] / 180 * Math.PI;
×
UNCOV
350
    const x = R * Math.sin(theta / 180 * Math.PI) * Math.sin(phi / 180 * Math.PI);
×
UNCOV
351
    const y = R * Math.cos(phi / 180 * Math.PI);
×
UNCOV
352
    const z = R * Math.sin(phi / 180 * Math.PI) * Math.cos(theta / 180 * Math.PI);
×
UNCOV
353
    const abs = Math.sqrt(z**2 + y**2);
×
UNCOV
354
    const arg = Math.atan(y / z) - thetaPrime;
×
UNCOV
355

×
356
    return [x, Math.sin(arg) * abs];
3✔
357
  }
3✔
358
}
3✔
359
export {BuildingShapeUtils};
3✔
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