-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCSVtoJSON.py
436 lines (368 loc) · 14.2 KB
/
CSVtoJSON.py
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
#Conversion utility for producing a CityJSON -file from CSV data
#By Juho-Pekka Virtanen, Aalto University, 2020
#Tested on Windows platform, Python 3.7.6, no external libraries should be required.
#Please see the README.md for further information
#Please see the LICENCE.txt for licence information
import csv
import json
print("Welcome")
print ("Please ensure that you use comma as separator.")
#Define path
print("Please input path to file for conversion (.csv)")
path = input()
#Define object title substring
print("Please provide name suffix for CityJSON objects")
subname = input()
#Lets open the file, see what we have...
try:
rowlist = []
with open(path, newline='') as csvfile:
CSVreader = csv.reader(csvfile, delimiter=',', quotechar='"')
for row in CSVreader:
rowlist.append(row)
except:
print("Reading file failed. Aborting")
exit()
print("Succesfully read treelist from: " +path)
print("Found a total of " +str(len(rowlist))+ " rows.")
print("Here's a small sample:")
print(rowlist[0])
print(rowlist[1])
print(rowlist[2])
#Query user for column order
print("Please indicate coordinate positions from columns:")
m = 0
while m < len(rowlist[0]):
print(str(m) + ": " + rowlist[0][m])
m = m + 1
print("Indicate X:")
xpos = input()
print("Indicate Y:")
ypos = input()
print("Indicate Z:")
zpos = input()
print("Indicate position of object ID:")
idpos = input()
print("Indicate position of sub-object index:")
subidpos = input()
try:
xpos = int(xpos)
ypos = int(ypos)
zpos = int(zpos)
idpos = int(idpos)
subidpos = int(subidpos)
if xpos >= len(rowlist[0]) and ypos >= len(rowlist[0]) and zpos >= len(rowlist[0]) and idpos >= len(rowlist[0]) and subidpos >= len(rowlist[0]):
print("Please input a valid selection.")
exit()
except:
print("Please input valid selection...")
exit()
#Define crop
print("Enable crop?")
print("0 = No, process everything")
print("1 = Yes, exclude objects outside processing boundary")
crop = input()
try:
crop = int(crop)
except:
print("Please input valid selection.")
exit()
if crop == 0:
print("Processing the whole thing.")
xmin = 0
xmax = 0
ymin = 0
ymax = 0
if crop == 1:
print("Please input crop range.")
print("X min:")
xmin = float(input())
print("X max:")
xmax = float(input())
print("Y min:")
ymin = float(input())
print("Y max:")
ymax = float(input())
if crop != 0 and crop != 1:
print("Please input valid selection.")
exit()
#Create empty dictionary
cityJSONRoot={}
#Set up CityJSON base structure
cityJSONRoot["type"] = "CityJSON"
cityJSONRoot["version"] = "1.0"
cityJSONRoot["CityObjects"] = ""
cityJSONRoot["vertices"] = ""
#Create empty objects that are to hold objects and vertices
cityJSONObjects = {}
cityJSONVertices = []
#Query user for processing style
print("Please specify what to process")
print("1 = Points (plot as rectangle or with a 3D tree symbol)")
print("2 = Polygons")
getSelection = input()
try:
getSelection = int(getSelection)
except:
print("Please input integer number.")
exit()
if (getSelection != 1) and (getSelection != 2):
print("Incorrect selection. Aborting.")
exit()
else:
if getSelection == 1:
objectType = "Tree"
if getSelection == 2:
objectType = "Area"
#This will track the amount of processed objects
writtenTrees = 0
#Processing track for polygonal areas
if objectType == "Area":
#Query user for flipping normals
print("Enable flipping normals?")
print("0 = No")
print("1 = Yes")
flip = input()
try:
flip = int(flip)
except:
print("Please input valid selection.")
exit()
#Query user for what object type to write
polygonObjectTypes = ["Building","BuildingPart","BuildingInstallation","Road","Railway","TransportSquare","WaterBody","LandUse","PlantCover","CityFurniture","GenericCityObject"]
print("Please select object type to use:")
m = 0
while m < len(polygonObjectTypes):
print(str(m) + ": " + polygonObjectTypes[m])
m = m + 1
selectedObjectType = input()
try:
selectedObjectType = int(selectedObjectType)
except:
print("Please input valid selection")
exit()
if selectedObjectType > len(polygonObjectTypes):
print("Incorrect.")
exit()
print("Selected: " + polygonObjectTypes[selectedObjectType])
#Read file
pointlist = []
with open(path, newline='') as csvfile:
CSVreader = csv.reader(csvfile, delimiter=',', quotechar='"')
for row in CSVreader:
pointlist.append(row)
i = 1 #Index holding the row to process, starts with 1 to skip header!
polyID = 1 #What polygon to process
polyPoints = []
polys = []
while i < len(pointlist): #Run as long as there are rows (e.g. points)
#Check if new polygon
if polyID == int(pointlist[i][idpos]):
polyPoints.append(pointlist[i])
i = i + 1
else:
if len(polyPoints) == 0: #Check for an empty polygon
print("Found an empty polygon at: " +str(polyID) + " skipping this.")
print(pointlist[i])
polyPoints = []
polyID = polyID + 1
else: #New polygon begings, re-initialize
polys.append(polyPoints)
polyPoints = []
polyID = polyID + 1
print("Processed polygons " +str(polyID-1))
print("Kept " +str(len(polys)))
#Processing an individual polygon. All metadata is taken from the first point, as they are assumed to remain constant
i = 0
pointIndex = 0 #Index holding the vertice number, used in assembling boundary
while i < len(polys):
polyPoints = polys[i]
subID = ""
subIDcount = 0
#Compute midpoint, used for cropping, if crop is enabled
xMed = 0
yMed = 0
n = 0
while n < len(polyPoints):
xMed = xMed + float(polyPoints[n][xpos])
yMed = yMed + float(polyPoints[n][ypos])
if polyPoints[n][subidpos] != subID:
subID = polyPoints[n][subidpos]
subIDcount = subIDcount + 1
n = n + 1
xMed = xMed / len(polyPoints)
yMed = yMed / len(polyPoints)
print("Polygon " + str(i) + " center at X=" +str(xMed) + " Y=" +str(yMed))
print("Polygon had " + str(subIDcount) + " sub-polygons.")
if (xmin < xMed < xmax and ymin < yMed < ymax) or crop == 0:
cityJSONAttributes = {}
#This just dumps all attributes from original file to CityJSON, note that is super-non-schema-compliant
m = 0
while m < len(polyPoints[0]):
if m != xpos and m != ypos and m != zpos and m != idpos:
cityJSONAttributes[pointlist[0][m]] = polyPoints[0][m]
m = m + 1
#Create dict for geometry attributes
cityJSONGeometryAttributes = {}
cityJSONGeometryAttributes["type"] = "MultiSurface"
cityJSONGeometryAttributes["lod"] = 2 #Note fixed LOD here!
#Asseble lists for point indexes to form boundary and sub-boundary list
#Sub boundary is used for objects that consists of multiple polygons
boundaryList = []
subBoundaryList = []
n = 0
subID = polyPoints[n][subidpos]
while n < len(polyPoints):
if polyPoints[n][subidpos] != subID:
if flip == 1: #Flipping normals now!
flippedList = []
m = 0
while m < len(subBoundaryList):
flippedList.append(subBoundaryList[len(subBoundaryList)-(m+1)])
m = m + 1
subBoundaryList = flippedList #Replace original boundary list with a flipped one
boundaryList.append([subBoundaryList])
subBoundaryList = [] #Begin new sub-boundary list
subBoundaryList.append(pointIndex) #Add points to new sb list
subID = polyPoints[n][subidpos] #Update index following which sb were are writing to
elif n == (len(polyPoints)-1): #Last point of polygon reached
subBoundaryList.append(pointIndex) #Still add to the same sb
if flip == 1: #Flipping normals now!
flippedList = []
m = 0
while m < len(subBoundaryList):
flippedList.append(subBoundaryList[len(subBoundaryList)-(m+1)])
m = m + 1
subBoundaryList = flippedList #Replace original boundary list with a flipped one
boundaryList.append([subBoundaryList]) #Close object
else:
subBoundaryList.append(pointIndex) #Add to same sb as before, we remain in same object
try:
cityJSONVertices.append([float(polyPoints[n][xpos]),float(polyPoints[n][ypos]),float(polyPoints[n][zpos])]) #This is to go to vertice list, note that it is shared for the entire file
writtenTrees = writtenTrees + 1
except:
print("Error encountered on this poly, writing Z = 0")
print(polyPoints[n])
cityJSONVertices.append([float(polyPoints[n][xpos]),float(polyPoints[n][ypos]),0.0]) #This is to go to vertice list, note that it is shared for the entire file
writtenTrees = writtenTrees + 1
pointIndex = pointIndex + 1
n = n + 1
cityJSONGeometryAttributes["boundaries"] = boundaryList
#Basic structure for objects
cityJSONObject = {}
cityJSONObject["type"] = polygonObjectTypes[selectedObjectType]
cityJSONObject["attributes"] = cityJSONAttributes
cityJSONObject["geometry"] = [cityJSONGeometryAttributes]
cityJSONObjects[polygonObjectTypes[selectedObjectType]+"_"+subname+"_"+polyPoints[1][idpos]] = cityJSONObject
i = i + 1
#This is the processing pipeline for points, such as tree objects
if objectType == "Tree":
treeList = []
with open(path, newline='') as csvfile:
CSVreader = csv.reader(csvfile, delimiter=',', quotechar='"')
for row in CSVreader:
treeList.append(row)
#Query user for lod-representation to use
print("Please specify model to utilize:")
print("1 = 2D rectangle")
print("2 = Simplified 3D tree icon")
treeModel = input()
try:
treeModel = int(treeModel)
except:
print("Please input integer number.")
exit()
if (treeModel != 1) and (treeModel != 2):
print("Incorrect selection. Aborting.")
exit()
#Default for tree size, later overwritten by what is found from data
treeScale = 1
#This is used to interpret Helsinki tree register size markings
sizeGroups = ["0_-_10_cm","20_-_30_cm","30_-_50_cm","50_-_70_cm","70_cm_-"]
# This is not smart, but remains here due to issues with original datasets.
# Should be fixed in the future to be more ubiquitous...
#Index to track how we progress in tree list
i = 1
while i < len(treeList):
y = float(treeList[i][xpos])
x = float(treeList[i][ypos])
z = float(treeList[i][zpos])
if (xmin < x < xmax and ymin < y < ymax) or crop == 0:
n = 0
while n < len(sizeGroups):
if treeList[i][9] == sizeGroups[n]:
treeScale = n * 3
n = n + 1
#Dict for object attributes
cityJSONAttributes = {}
#This just dumps all attributes from original file to CityJSON, note that is super-non-schema-compliant
m = 0
while m < len(treeList[i]):
if m != xpos and m != ypos and m != zpos and m != idpos:
cityJSONAttributes[treeList[i][m]] = treeList[i][m]
m = m + 1
#Dict for geometry attributes
cityJSONGeometryAttributes = {}
cityJSONGeometryAttributes["type"] = "MultiSurface"
cityJSONGeometryAttributes["lod"] = treeModel
#Create geometry for rectangular patch
if treeModel == 1:
cityJSONGeometryAttributes["boundaries"] = [[[writtenTrees*4,writtenTrees*4+1,writtenTrees*4+2,writtenTrees*4+3]]]
#Create geometry for a small 3D tree symbol.
if treeModel == 2:
cityJSONGeometryAttributes["boundaries"] = [
[[writtenTrees*13+0,writtenTrees*13+2,writtenTrees*13+5,writtenTrees*13+3]],
[[writtenTrees*13+2,writtenTrees*13+1,writtenTrees*13+4,writtenTrees*13+5]],
[[writtenTrees*13+1,writtenTrees*13+0,writtenTrees*13+3,writtenTrees*13+4]],
[[writtenTrees*13+3,writtenTrees*13+5,writtenTrees*13+8,writtenTrees*13+6]],
[[writtenTrees*13+5,writtenTrees*13+4,writtenTrees*13+7,writtenTrees*13+8]],
[[writtenTrees*13+4,writtenTrees*13+3,writtenTrees*13+6,writtenTrees*13+7]],
[[writtenTrees*13+6,writtenTrees*13+8,writtenTrees*13+11,writtenTrees*13+9]],
[[writtenTrees*13+8,writtenTrees*13+7,writtenTrees*13+10,writtenTrees*13+11]],
[[writtenTrees*13+7,writtenTrees*13+6,writtenTrees*13+9,writtenTrees*13+10]],
[[writtenTrees*13+9,writtenTrees*13+11,writtenTrees*13+12]],
[[writtenTrees*13+11,writtenTrees*13+10,writtenTrees*13+12]],
[[writtenTrees*13+10,writtenTrees*13+9,writtenTrees*13+12]],
]
#In future, this should be expressed as templates for performance improvements in visualization.
#Assebling the object
cityJSONObject = {}
cityJSONObject["type"] = "SolitaryVegetationObject"
cityJSONObject["attributes"] = cityJSONAttributes
cityJSONObject["geometry"] = [cityJSONGeometryAttributes]
cityJSONObjects["Tree_" + subname + "_" + treeList[i][1]] = cityJSONObject
#Add vertices for rectangular patch
if treeModel == 1:
cityJSONVertices.append([x+treeScale,y+treeScale,z])
cityJSONVertices.append([x-treeScale,y+treeScale,z])
cityJSONVertices.append([x-treeScale,y-treeScale,z])
cityJSONVertices.append([x+treeScale,y-treeScale,z])
#Add vertices for tree symbol
if treeModel == 2:
cityJSONVertices.append([x+0.000000*treeScale,y-0.093815*treeScale,z+0.000000*treeScale])
cityJSONVertices.append([x-0.081246*treeScale,y+0.046907*treeScale,z+0.000000*treeScale])
cityJSONVertices.append([x+0.081246*treeScale,y+0.046907*treeScale,z+0.000000*treeScale])
cityJSONVertices.append([x+0.000000*treeScale,y-0.093815*treeScale,z+0.295725*treeScale])
cityJSONVertices.append([x-0.081246*treeScale,y+0.046907*treeScale,z+0.295725*treeScale])
cityJSONVertices.append([x+0.081246*treeScale,y+0.046907*treeScale,z+0.295725*treeScale])
cityJSONVertices.append([x-0.000000*treeScale,y-0.351669*treeScale,z+0.696748*treeScale])
cityJSONVertices.append([x-0.304555*treeScale,y+0.175835*treeScale,z+0.696748*treeScale])
cityJSONVertices.append([x+0.304555*treeScale,y+0.175835*treeScale,z+0.696748*treeScale])
cityJSONVertices.append([x-0.000000*treeScale,y-0.284935*treeScale,z+0.914895*treeScale])
cityJSONVertices.append([x-0.246761*treeScale,y+0.142468*treeScale,z+0.914895*treeScale])
cityJSONVertices.append([x+0.246761*treeScale,y+0.142468*treeScale,z+0.914895*treeScale])
cityJSONVertices.append([x+0.000000*treeScale,y-0.000000*treeScale,z+1.000180*treeScale])
writtenTrees = writtenTrees + 1
i = i + 1
#Assembling the entire file
cityJSONRoot["CityObjects"] = cityJSONObjects
cityJSONRoot["vertices"] = cityJSONVertices
#Path to write to, overwritten if there already
outPath = path + ".json"
#Open write, write, close write
write = open(outPath,"w")
write.write(json.dumps(cityJSONRoot,indent=1))
write.close()
#Print some stats.
print("Processed a total of " + str(i-1) + " objects.")
print("Wrote out a total of " + str(writtenTrees) + " points.")