-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlevel-to-georender.js
190 lines (177 loc) · 5.39 KB
/
level-to-georender.js
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
const fs = require('fs')
const path = require('path')
const {encode} = require('georender-pack')
const varint = require('varint')
const osmItemKey = require('./level-osm-item-key')
// read notes below about the checks that are made on incoming data
const enforceCheck = true
// {
// leveldb : level,
// georender : string | WriteStream,
// format : 'base64'|'hex',
// debug : boolean,
// id: string|[string]|undefined
// } => undefined
// Write the contents of the OSM levelleveldb into a georender
// encoded new line delimited file
module.exports = async function levelToGeorender ({
leveldb,
georender,
format='base64',
debug=false,
id,
}) {
const createWriteStream = (filePath) => {
fs.mkdirSync(path.dirname(filePath), { recursive: true })
return fs.createWriteStream(filePath)
}
const target = typeof georender === 'string'
? createWriteStream(georender)
: georender && typeof georender.write === 'function' && typeof georender.end === 'function'
? georender // has write & end, we can use it to write data
: process.stdout
const itemsIteratorForIds = async (idArray) => {
const items = await leveldb.getMany(idArray)
return () => items[Symbol.iterator]()
}
const items = Array.isArray(id)
? await itemsIteratorForIds(id)
: typeof id === 'string'
? await itemsIteratorForIds([id])
: () => leveldb.values()
const getDep = (item) => {
const key = osmItemKey(item)
return new Promise((resolve, reject) => {
leveldb.get(key, (error, value) => {
if (error) {
if (debug) console.log('get-id:error:', key)
return resolve()
}
if (!value && debug) console.log('get-dep:not-found:', key)
resolve(value)
})
})
}
const getRefs = async (item, deps) => {
if (!Array.isArray(item.refs) || item.refs.length === 0) return Promise.resolve()
const getters = item.refs.map((refId) => {
return new Promise(async (resolve, reject) => {
try {
const refItem = await getDep({ type: 'node', id: refId })
if (refItem) {
deps[refId] = refItem
}
resolve()
}
catch (error) {
reject(error)
}
})
})
return await Promise.all(getters)
}
const getMembers = async (item, deps) => {
const getters = item.members
.filter((member) => {
return member && member.id && member.type === 'way' &&
(member.role === 'inner' || member.role === 'outer')
})
.map((member) => {
return new Promise(async (resolve, reject) => {
try {
const way = await getDep(member)
if (way) {
deps[member.id] = way
await getRefs(way, deps)
}
resolve()
}
catch (error) {
reject(error)
}
})
})
return await Promise.all(getters)
}
try {
for await (const item of items()) {
const deps = {}
if (item.type === 'node') {
// no deps
}
else if (item.type === 'way') {
await getRefs(item, deps)
}
else if (item.type === 'relation') {
await getMembers(item, deps)
}
const encodedItem = encode(item, deps)
if (!encodedItem) {
if (debug) console.log('no-encoded-item', item.id, item.type)
continue
}
if (enforceCheck) {
const pnt = getPoint(encodedItem)
if (pnt === null) {
if (debug) console.log('null-point', item.id, item.type)
continue
}
const valid = validPoint(pnt)
if (!valid) {
if (debug) console.log('nan-point', item.id, item.type)
continue
}
}
target.write(encodedItem.toString(format) + '\n')
}
target.end()
}
catch (error) {
throw (error)
}
// plucked from georender-eyros, should better understand
// why we might get a null or nan point as described above
// and if we should filter from them in georender-eyros, or
// prevent them from occuring upstream
function getPoint(buf) {
var ft = buf[0]
if (ft !== 1 && ft !== 1 && ft !== 2 && ft !== 3 && ft !== 4) return null
var offset = 1
var t = varint.decode(buf, offset)
offset += varint.decode.bytes
var id = varint.decode(buf, offset)
offset += varint.decode.bytes
if (ft === 0x01) {
var lon = buf.readFloatLE(offset)
offset += 4
var lat = buf.readFloatLE(offset)
offset += 4
return [lon,lat]
} else if (ft === 0x02 || ft === 0x03 || ft === 0x04) {
var pcount = varint.decode(buf, offset)
offset += varint.decode.bytes
if (pcount === 0) return null
var point = [[Infinity,-Infinity],[Infinity,-Infinity]]
for (var i = 0; i < pcount; i++) {
var lon = buf.readFloatLE(offset)
offset += 4
var lat = buf.readFloatLE(offset)
offset += 4
point[0][0] = Math.min(point[0][0], lon)
point[0][1] = Math.max(point[0][1], lon)
point[1][0] = Math.min(point[1][0], lat)
point[1][1] = Math.max(point[1][1], lat)
}
if (pcount === 1) return point[0]
return point
}
return null
}
function isNumber (v) {
return typeof v === 'number' && !isNaN(v)
}
function validPoint (pnt) {
if (!Array.isArray(pnt)) return false
return pnt.flat().find(p => !isNumber(p)) === undefined
}
}