File size: 3,408 Bytes
9ae1216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
 * WKB (Well-Known Binary) decoder for geometry objects.
 *
 * @param {DataReader} reader
 * @returns {Geometry} geometry object
 */
export function wkbToGeojson(reader) {
  const flags = getFlags(reader)

  if (flags.type === 1) { // Point
    return { type: 'Point', coordinates: readPosition(reader, flags) }
  } else if (flags.type === 2) { // LineString
    return { type: 'LineString', coordinates: readLine(reader, flags) }
  } else if (flags.type === 3) { // Polygon
    return { type: 'Polygon', coordinates: readPolygon(reader, flags) }
  } else if (flags.type === 4) { // MultiPoint
    const points = []
    for (let i = 0; i < flags.count; i++) {
      points.push(readPosition(reader, getFlags(reader)))
    }
    return { type: 'MultiPoint', coordinates: points }
  } else if (flags.type === 5) { // MultiLineString
    const lines = []
    for (let i = 0; i < flags.count; i++) {
      lines.push(readLine(reader, getFlags(reader)))
    }
    return { type: 'MultiLineString', coordinates: lines }
  } else if (flags.type === 6) { // MultiPolygon
    const polygons = []
    for (let i = 0; i < flags.count; i++) {
      polygons.push(readPolygon(reader, getFlags(reader)))
    }
    return { type: 'MultiPolygon', coordinates: polygons }
  } else if (flags.type === 7) { // GeometryCollection
    const geometries = []
    for (let i = 0; i < flags.count; i++) {
      geometries.push(wkbToGeojson(reader))
    }
    return { type: 'GeometryCollection', geometries }
  } else {
    throw new Error(`Unsupported geometry type: ${flags.type}`)
  }
}

/**
 * Extract ISO WKB flags and base geometry type.
 *
 * @param {DataReader} reader
 * @returns {WkbFlags}
 */
function getFlags(reader) {
  const { view } = reader
  const littleEndian = view.getUint8(reader.offset++) === 1
  const rawType = view.getUint32(reader.offset, littleEndian)
  reader.offset += 4

  const type = rawType % 1000
  const flags = Math.floor(rawType / 1000)

  let count = 0
  if (type > 1 && type <= 7) {
    count = view.getUint32(reader.offset, littleEndian)
    reader.offset += 4
  }

  // XY, XYZ, XYM, XYZM
  let dim = 2
  if (flags) dim++
  if (flags === 3) dim++

  return { littleEndian, type, dim, count }
}

/**
 * @param {DataReader} reader
 * @param {WkbFlags} flags
 * @returns {number[]}
 */
function readPosition(reader, flags) {
  const points = []
  for (let i = 0; i < flags.dim; i++) {
    const coord = reader.view.getFloat64(reader.offset, flags.littleEndian)
    reader.offset += 8
    points.push(coord)
  }
  return points
}

/**
 * @param {DataReader} reader
 * @param {WkbFlags} flags
 * @returns {number[][]}
 */
function readLine(reader, flags) {
  const points = []
  for (let i = 0; i < flags.count; i++) {
    points.push(readPosition(reader, flags))
  }
  return points
}

/**
 * @param {DataReader} reader
 * @param {WkbFlags} flags
 * @returns {number[][][]}
 */
function readPolygon(reader, flags) {
  const { view } = reader
  const rings = []
  for (let r = 0; r < flags.count; r++) {
    const count = view.getUint32(reader.offset, flags.littleEndian)
    reader.offset += 4
    rings.push(readLine(reader, { ...flags, count }))
  }
  return rings
}

/**
 * @typedef {object} WkbFlags
 * @property {boolean} littleEndian
 * @property {number} type
 * @property {number} dim
 * @property {number} count
 */

/**
 * @import {DataReader, Geometry} from '../src/types.js'
 */