1 | var util = require('util');
|
2 | var stream = require('stream');
|
3 | var RGBColor = require('rgbcolor');
|
4 | var sax = require("sax");
|
5 |
|
6 | function getRGB(osmColor) {
|
7 | if (osmColor) {
|
8 | var hexRGB = /^#?([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})$/i.exec(osmColor);
|
9 | if (hexRGB) {
|
10 | return "rgb("
|
11 | + (parseInt(hexRGB[1], 16)) + ","
|
12 | + (parseInt(hexRGB[2], 16)) + ","
|
13 | + (parseInt(hexRGB[3], 16)) + ")";
|
14 | }
|
15 | var color = new RGBColor(osmColor);
|
16 | if (color.ok) {
|
17 | return color.toRGB();
|
18 | }
|
19 | }
|
20 | return "rgb(0,0,0)";
|
21 | }
|
22 |
|
23 | var geoBlds = {};
|
24 | function heightToMeter(height) {
|
25 | "use strict";
|
26 | var result = 1,
|
27 | unit = height.substr(height.length - 1);
|
28 | switch (unit) {
|
29 | case 'm':
|
30 | result = height.substr(0, height.length - 1);
|
31 | break;
|
32 | default:
|
33 | result = height;
|
34 | break;
|
35 | }
|
36 | return result;
|
37 | }
|
38 | function getGeoBuilding(id) {
|
39 | "use strict";
|
40 | if (!(geoBlds.hasOwnProperty(id))) {
|
41 | var geoBld = {
|
42 | "type": "FeatureCollection",
|
43 | "features": [],
|
44 | properties: {
|
45 | "id": id,
|
46 | "type": "building"
|
47 | }
|
48 | };
|
49 | geoBlds[id] = geoBld;
|
50 | }
|
51 | return geoBlds[id];
|
52 | }
|
53 |
|
54 | function removeBuilding(id) {
|
55 | "use strict";
|
56 | if (geoBlds.hasOwnProperty(id)) {
|
57 | delete geoBlds[id];
|
58 | }
|
59 | }
|
60 |
|
61 | var geoGroundBlock = {
|
62 | "type": "Feature",
|
63 | "geometry": {
|
64 | "type": "Polygon",
|
65 | "coordinates": [[[-73.9860386, 40.7487894], [-73.9860386, 40.7487894], [-73.9860386, 40.7487894], [-73.9860386, 40.7487894]]]
|
66 | },
|
67 | "properties": {
|
68 | "type": "ground"
|
69 | }
|
70 | };
|
71 | var geoBldPart = {
|
72 | "type": "Feature",
|
73 | "geometry": {
|
74 | "type": "Polygon",
|
75 | "coordinates": []
|
76 | },
|
77 | "properties": {
|
78 | "type": "buildingPart",
|
79 | "minHeight": 20,
|
80 | "minLevel": 5,
|
81 | "color": "rgb(223, 55, 54)",
|
82 | "levels": 20,
|
83 | "height": 75
|
84 | }
|
85 | };
|
86 | var geoRoof = {
|
87 | "type": "Feature",
|
88 | "geometry": {
|
89 | "type": "Polygon",
|
90 | "coordinates": []
|
91 | },
|
92 | "properties": {
|
93 | "type": "geoRoof",
|
94 | "color": "rgb(221, 123, 56)",
|
95 | "roofShape": "flat",
|
96 | "maxHeight": 95
|
97 | }
|
98 | };
|
99 | var geoBld = {
|
100 | "type": "FeatureCollection",
|
101 | "features": [geoBldPart, geoRoof],
|
102 | "properties": {
|
103 | "id": "2098969",
|
104 | "type": "building",
|
105 | "name": "Empire State Building"
|
106 | }
|
107 | };
|
108 |
|
109 | function convert(onConvert) {
|
110 | "use strict";
|
111 | var xmlStream = sax.createStream(true);
|
112 | xmlStream.on('error', function (error) {
|
113 | console.error("error!", error);
|
114 | this._parser.error = null;
|
115 | this._parser.resume();
|
116 | });
|
117 | var blocks = [],
|
118 | geoBldParts = {},
|
119 | geoRoofs = {},
|
120 | nodeMap = {},
|
121 | onWay = false,
|
122 | onRelation = false;
|
123 | var way = {};
|
124 | var relation = {};
|
125 | xmlStream.on('opentag', function (node) {
|
126 | var name = node.name, attributes = node.attributes;
|
127 | if (name === 'node') {
|
128 | var id = attributes.id;
|
129 | var lat = attributes.lat;
|
130 | var lon = attributes.lon;
|
131 | nodeMap[id] = [+lon, +lat];
|
132 | } else if (name === 'way') {
|
133 | way.nodes = [];
|
134 | way.id = attributes.id;
|
135 | way.isBld = false;
|
136 | way.name = undefined;
|
137 | way.color = undefined;
|
138 | way.osmBldPartHeight = undefined;
|
139 | way.minHeight = 0;
|
140 | way.bldLevels = undefined;
|
141 | way.bldMinLevel = 0;
|
142 | way.roofHeight = undefined;
|
143 | way.roofShape = "flat";
|
144 | onWay = true;
|
145 | } else if (name === 'nd' && onWay) {
|
146 | if (!nodeMap.hasOwnProperty(attributes.ref)) {
|
147 | console.log("ERROR: Cannot link way to this node: " + attributes.ref);
|
148 | } else {
|
149 | way.nodes[way.nodes.length] = nodeMap[attributes.ref];
|
150 | }
|
151 | } else if (name === 'tag' && onWay) {
|
152 | if (attributes.k === 'building') {
|
153 | way.isBld = true;
|
154 | } else if (attributes.k === 'building:levels') {
|
155 | way.bldLevels = attributes.v;
|
156 | } else if (attributes.k === 'building:min_level') {
|
157 | way.bldMinLevel = attributes.V;
|
158 | } else if (attributes.k === 'height') {
|
159 | way.osmBldPartHeight = heightToMeter(attributes.v);
|
160 | } else if (attributes.k === 'min_height') {
|
161 | way.minHeight = heightToMeter(attributes.v);
|
162 | } else if (attributes.k === 'name') {
|
163 | way.name = attributes.v;
|
164 | } else if (attributes.k === 'building:colour') {
|
165 | way.color = attributes.v;
|
166 | } else if (attributes.k === 'roof:shape') {
|
167 | way.roofShape = attributes.v;
|
168 | } else if (attributes.k === 'roof:height') {
|
169 | way.roofHeight = attributes.v;
|
170 | }
|
171 | } else if (name === 'relation') {
|
172 | relation.id = attributes.id;
|
173 | relation.isBld = false;
|
174 | relation.osmBldPartHeight = undefined;
|
175 | relation.minHeight = undefined;
|
176 | relation.name = undefined;
|
177 | relation.pptRoofHeight = undefined;
|
178 | relation.roofShape = 'flat';
|
179 | onRelation = true;
|
180 | } else if (onRelation && name === 'member' && attributes.type === 'way') {
|
181 | if (attributes.ref in geoBldParts) {
|
182 | var geoBld = getGeoBuilding(relation.id);
|
183 | geoBld.features[geoBld.features.length] = geoBldParts[attributes.ref];
|
184 | if (geoRoofs[attributes.ref]) {
|
185 | geoBld.features[geoBld.features.length] = geoRoofs[attributes.ref];
|
186 | }
|
187 | }
|
188 | } else if (onRelation && name === 'tag' && attributes.k === 'name') {
|
189 | relation.name = attributes.v;
|
190 | } else if (onRelation && name === 'tag' && attributes.k === 'building:part' && attributes.v === 'yes') {
|
191 | relation.isBld = true;
|
192 | } else if (onRelation && name === 'tag' && attributes.k === 'building' && attributes.v === 'yes') {
|
193 | relation.isBld = true;
|
194 | } else if (onRelation && name === 'tag' && attributes.k === 'type' && attributes.v === 'building') {
|
195 | relation.isBld = true;
|
196 | } else if (onRelation && name === 'tag' && attributes.k === 'height') {
|
197 | relation.osmBldPartHeight = heightToMeter(attributes.v);
|
198 | } else if (onRelation && name === 'tag' && attributes.k === 'min_height') {
|
199 | relation.minHeight = heightToMeter(attributes.v);
|
200 | } else if (onRelation && name === 'tag' && attributes.k === 'roof:shape') {
|
201 | relation.roofShape = attributes.v;
|
202 | } else if (onRelation && name === 'tag' && attributes.k === 'roof:height') {
|
203 | relation.optRoofHeight = attributes.v;
|
204 | }
|
205 | });
|
206 | xmlStream.on('closetag', function (name) {
|
207 | if (name === 'way') {
|
208 | onWay = false;
|
209 | if (way.roofShape) {
|
210 | var geoRoof = {
|
211 | "type": "Feature",
|
212 | "geometry": {
|
213 | "type": "Polygon",
|
214 | "coordinates": [way.nodes]
|
215 | },
|
216 | "properties": {
|
217 | "id": way.id,
|
218 | "type": "geoRoof",
|
219 | "color": getRGB(way.color),
|
220 | "roofShape": way.roofShape,
|
221 | "height": way.osmBldPartHeight
|
222 | }
|
223 | };
|
224 | }
|
225 | var geoBldPart = {
|
226 | "type": "Feature",
|
227 | "geometry": {
|
228 | "type": "Polygon",
|
229 | "coordinates": [way.nodes]
|
230 | },
|
231 | "properties": {
|
232 | "id": way.id,
|
233 | "type": "buildingPart",
|
234 | "minHeight": way.minHeight,
|
235 | "minLevel": way.bldMinLevel,
|
236 | "name": way.name,
|
237 | "color": getRGB(way.color),
|
238 | "levels": way.bldLevels,
|
239 | "height": way.osmBldPartHeight
|
240 | }
|
241 | };
|
242 | if (way.isBld) {
|
243 | var geoBld = getGeoBuilding(way.id);
|
244 | geoBld.features[geoBld.features.length] = geoBldPart;
|
245 | if (geoRoof) {
|
246 | geoBld.features[geoBld.features.length] = geoRoof;
|
247 | }
|
248 | blocks[blocks.length] = JSON.parse(JSON.stringify(geoBld));
|
249 | } else {
|
250 | geoBldParts[way.id] = geoBldPart;
|
251 | if (geoRoof) {
|
252 | geoRoofs[way.id] = geoRoof;
|
253 | }
|
254 | }
|
255 | } else if (name === 'relation') {
|
256 | if (!relation.isBld) {
|
257 | removeBuilding(relation.id);
|
258 | } else {
|
259 | var geoBld = getGeoBuilding(relation.id);
|
260 | if (relation.osmBldPartHeight !== undefined) {
|
261 | geoBld.properties.name = relation.name;
|
262 | for (var i = 0; i < geoBld.features.length; i++) {
|
263 | var geoBldPart = geoBld.features[i];
|
264 | if (geoBldPart.properties.height !== undefined) {
|
265 | geoBldPart.properties.height = relation.osmBldPartHeight;
|
266 | }
|
267 | if (geoBldPart.properties.minHeight === undefined) {
|
268 | geoBldPart.properties.minHeight = relation.minHeight;
|
269 | }
|
270 | }
|
271 | }
|
272 | blocks[blocks.length] = JSON.parse(JSON.stringify(geoBld));
|
273 | }
|
274 | onRelation = false;
|
275 | } else if (name === 'osm') {
|
276 | if (onConvert !== undefined) {
|
277 | onConvert(blocks);
|
278 | }
|
279 | }
|
280 | });
|
281 | return xmlStream;
|
282 | }
|
283 |
|
284 |
|
285 | exports.convert = convert;
|