This repository has been archived by the owner on Apr 2, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
PbfSplicer.js
177 lines (152 loc) · 5.33 KB
/
PbfSplicer.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
const _ = require('underscore');
const tileCodec = require('./tileCodec');
function calcKeyIndex(value, store, lookup) {
let ind = lookup[value];
if (ind === undefined) {
ind = store.length;
// eslint-disable-next-line no-param-reassign
lookup[value] = ind;
store.push(value);
}
return ind;
}
function calcValueIndex(value, store, lookup) {
let ind = lookup[value.tag - 1][value.value];
if (ind === undefined) {
ind = store.length;
// eslint-disable-next-line no-param-reassign
lookup[value.tag - 1][value.value] = ind;
store.push(value);
}
return ind;
}
/**
* Transform vector tile tags
* @param {object} options
* @param {string} options.nameTag
* @param {string} options.multiTag
* @param {LanguagePicker} [options.namePicker]
* @constructor
*/
function PbfSplicer(options) {
// tag which will be auto-removed and auto-injected. Usually 'name'
this.nameTag = options.nameTag;
// tag that contains JSON initially, and which works as a prefix for multiple values
this.multiTag = options.multiTag;
// If options.namePicker is given, this class converts multiple language tags into one
// Otherwise, it assumes that a single name_ tag exists with JSON content, and it will replace
// it with multiple tags "name_en", "name_fr", ... depending on the JSON language codes
this.namePicker = options.namePicker;
// Flag to make requested_name (local_name) form
this.combineName = options.combineName;
}
PbfSplicer.prototype.processTile = function processTile(data) {
const self = this;
const tile = tileCodec.decodeTile(data);
let changed;
for (const layer of tile.layers) {
// Optimization - don't process layer if it has no relevant tags
if (!_.any(layer.keys, key => key.startsWith(self.multiTag))) {
// eslint-disable-next-line no-continue
continue;
}
changed = true;
const newKeys = [];
const newValues = [];
const newKeysLookup = {};
// One for each data type like STRING, FLOAT, ... (7 total)
const newValuesLookup = [{}, {}, {}, {}, {}, {}, {}];
const addValueFunc = (key, value, tagArray) => {
tagArray.push(calcKeyIndex(key, newKeys, newKeysLookup));
tagArray.push(calcValueIndex(value, newValues, newValuesLookup));
};
for (const feature of layer.features) {
const { tags } = feature;
if ((tags.length % 2) !== 0) {
throw new Error(`Broken tile - tags count ${tags.length}`);
}
if (self.namePicker) {
feature.tags = self.pickOneLanguage(layer, tags, addValueFunc);
} else {
feature.tags = self.jsonTagToMultiple(layer, tags, addValueFunc);
}
}
layer.keys = newKeys;
layer.values = newValues;
}
return !changed ? data : tileCodec.encodeTile(tile);
};
PbfSplicer.prototype.jsonTagToMultiple = function jsonTagToMultiple(layer, tags, addValueFunc) {
const newTags = [];
let jsonTag;
for (let ind = 0; ind < tags.length; ind += 2) {
const key = layer.keys[tags[ind]];
const value = layer.values[tags[ind + 1]];
if (key === this.multiTag) {
if (value.tag !== 1) {
throw new Error('Expecting a tag of type STRING');
} else if (jsonTag !== undefined) {
throw new Error(`Duplicate tags ${this.multiTag}`);
}
// Decode this value into multiple tags
jsonTag = JSON.parse(value.value);
if (jsonTag === null || typeof jsonTag !== 'object' || Array.isArray(jsonTag)) {
throw new Error(`Tag "${this.multiTag}" must be a JSON object {"lang-code": "value", ...}`);
}
// Expand object into multiple tags
for (const lang of Object.keys(jsonTag)) {
addValueFunc(this.multiTag + lang, { tag: 1, value: jsonTag[lang] }, newTags);
}
} else {
addValueFunc(key, value, newTags);
}
}
return newTags;
};
/**
* Replace all "name_*" tags with the most appropriate "name" tag.
* @param {object} layer
* @param {int[]} tags
* @param {function} addValueFunc
* @return {int[]}
*/
PbfSplicer.prototype.pickOneLanguage = function pickOneLanguage(layer, tags, addValueFunc) {
const newTags = [];
const langPicker = this.namePicker.newProcessor();
let nameValue;
for (let ind = 0; ind < tags.length; ind += 2) {
const key = layer.keys[tags[ind]];
const value = layer.values[tags[ind + 1]];
if (key === this.nameTag) {
if (value.tag !== 1) {
throw new Error('Expecting a tag of type STRING');
}
nameValue = value;
langPicker.addValue(key, value);
} else if (!key.startsWith(this.multiTag)) {
// Keep all non "name_*" tags
addValueFunc(key, value, newTags);
} else {
if (value.tag !== 1) {
throw new Error('Expecting a tag of type STRING');
}
langPicker.addValue(key, value);
}
}
const langValue = langPicker.getResult();
if (this.combineName
&& nameValue && langValue
&& nameValue.value !== langValue.value) {
// combined format: translated name (local name)
const combinedNameTag = { value: `${langValue.value} (${nameValue.value})`, tag: nameValue.tag };
addValueFunc(this.nameTag, combinedNameTag, newTags);
} else if (langValue) {
// only translated name
addValueFunc(this.nameTag, langValue, newTags);
} else if (nameValue) {
// only local name
addValueFunc(this.nameTag, nameValue, newTags);
}
return newTags;
};
module.exports = PbfSplicer;