-
Notifications
You must be signed in to change notification settings - Fork 2
/
Tables.rsc
688 lines (537 loc) · 17.3 KB
/
Tables.rsc
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
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
/*
These macros provide a layer of abstraction to work with table data
in GISDK. Think something like a basic tidyr.
Once a table is created, you could calculate a new density column
like so:
TABLE.density = TABLE.population / TABLE.area
*/
Macro "test"
a_colNames = {"col1", "col2"}
a_data = {A2V({1, 2, 3}), A2V({"one", "two", "three"})}
// Test table creation
cTABLE = RunMacro("Create Table", a_colNames, a_data)
// Test table read
dir = "C:\\Users\\warddk\\Documents\\SAG\\Tools\\GISDK Tables"
view = OpenTable("tbl", "FFB", {dir + "\\FreeFlowSpeed.bin", })
rTABLE = RunMacro("Read Table", view)
CloseView(view)
// Test calculation and table write
rTABLE.test = rTABLE.PSTtoFFMod * 2
RunMacro("Write Table", rTABLE, dir + "\\test.csv", )
ShowMessage("Done with test")
EndMacro
/*
Create Table
Take field names and vectors and create a table in memory
Returns a GISDK options array that represents a table.
a_colNames: Array of strings of column names
a_data: array of vectors. Each must be the same length
See the "View to Table" function for creating a table object
from an existing view. See "Matrix to Table" for matrix
conversion.
*/
Macro "Create Table" (a_colNames, a_data)
// Check for required arguments
if a_colNames = null or a_data = null then do
Throw("Required argument missing or null.")
end
// Check dimensions of the three input arrays
length = a_colNames.length
if a_data.length <> length then do
Throw("Create Table: Input arrays must have same length.")
end
// Check vector lengths
for i = 1 to a_data.length do
if i = 1 then length = a_data[i].length
else do
if length <> a_data[i].length then do
Throw("Create Table: Not all data vectors have equal length.")
end
end
end
// Create a table object
TABLE = null
for c = 1 to a_colNames.length do
name = a_colNames[c]
vec = a_data[c]
TABLE.(name) = vec
end
return(TABLE)
EndMacro
/*
This macro writes a table object to csv
file (string): file name
TABLE (array) object
append (string): If true, then append data to file.
*/
Macro "Write Table" (TABLE, file, append)
// Check for required arguments
if TABLE = null or file = null then do
Throw("Required argument missing or null.")
end
// Check TABLE to make sure all vectors are the same length
for i = 1 to TABLE.length do
if i = 1 then length = TABLE[i][2].length
else do
if length <> TABLE[i][2].length then do
Throw("Write Table: Not all columns have equal length.")
end
end
end
// Check that the file name ends in CSV
if Right(file, 3) <> "csv" then do
Throw("Write Table: File must be a CSV")
end
// Open a csv file for writing
if append then file = OpenFile(file, "a")
else file = OpenFile(file, "w")
// Write the row of column names
for i = 1 to TABLE.length do
if i = 1 then firstLine = TABLE[i][1]
else firstLine = firstLine + "," + TABLE[i][1]
end
WriteLine(file, firstLine)
// Write each remaining row
for r = 1 to TABLE[1][2].length do
line = null
for c = 1 to TABLE.length do
type = TABLE[c][2].type
if type = "string" then strVal = TABLE[c][2][r]
else strVal = String(TABLE[c][2][r])
if c = 1 then line = strVal
else line = line + "," + strVal
end
WriteLine(file, line)
end
CloseFile(file)
EndMacro
/*
View to Table
This macro converts a view into a table object.
view (string): TC view name
set (string): optional set name
*/
Macro "View to Table" (view, set)
// Check for required arguments
if view = null then do
Throw("Required argument 'view' missing or null.")
end
a_fields = GetFields(view, )
a_fields = a_fields[1]
TABLE = null
for f = 1 to a_fields.length do
field = a_fields[f]
// When a view has too many rows, a "???" will appear in the editor
// meaning that TC did not load the entire view into memory.
// Creating a selection set will force TC to load the entire view.
if f = 1 then do
SetView(view)
qry = "Select * where nz(" + field + ") >= 0"
SelectByQuery("temp", "Several", qry)
end
TABLE.(field) = GetDataVector(view + "|" + set, field, )
end
return(TABLE)
EndMacro
/*
Converts a matrix to a table object.
mtxcur: a matrix currency that fully defines the matrix
handle, core name, and row/column index to convert.
*/
Macro "Matrix to Table" (mtxcur)
// Validate arguments
if mtxcur.matrix.Name = null then do
Throw("mtxcur variable must be a matrix currency")
end
// Create a temporary bin file
file_name = GetTempFileName(".bin")
// Set the matrix index and export to a table
SetMatrixIndex(mtxcur.matrix, mtxcur.rowindex, mtxcur.colindex)
opts = null
opts.Tables = {mtxcur.corename}
CreateTableFromMatrix(mtxcur.matrix, file_name, "FFB", opts)
// Open exported table into view
view = OpenTable("view", "FFB", {file_name})
// Read the view into a table
TABLE = RunMacro("View to Table", view)
// Clean up workspace
CloseView(view)
DeleteFile(file_name)
DeleteFile(Substitute(file_name, ".bin", ".DCB", ))
return(TABLE)
EndMacro
/*
Performs the equivalent of vlookup in excel,
similar to match() in R.
input: a vector/array contiaining the values that will be looked up
index: a vector/array the same length as value. Determines position.
value: a vector/array of the values to find and return.
Returns a vector the same length as "input" that contains the
values from the value vector.
*/
Macro "Match" (input, index, value)
// Make sure arguments are vectors or arrays
if TypeOf(input) <> "array" and TypeOf(input) <> "vector" then do
Throw("input must be either a vector or array.")
end else if TypeOf(index) <> "array" and TypeOf(index) <> "vector" then do
Throw("index must be either a vector or array.")
end else if TypeOf(value) <> "array" and TypeOf(value) <> "vector" then do
Throw("value must be either a vector or array.")
end
// If inputs are vectors, convert to arrays
if TypeOf(input) = "vector" then input = V2A(input)
if TypeOf(index) = "vector" then index = V2A(index)
if TypeOf(value) = "vector" then value = V2A(value)
// Make sure v_index and value are same length
if index.length <> value.length then do
Throw("v_index and value must be same length.")
end
// Perform the match
for i = 1 to input.length do
in = input[i]
pos = ArrayPosition(index, {in}, )
ret = value[pos]
a_ret = a_ret + {ret}
end
return(A2V(a_ret))
EndMacro
/*
Takes a TBL object and makes sure all data is stored in a vector.
This is useful if the table object was built using arrays to enable
vector math.
TABLE: table object to be checked/converted
*/
Macro "Vectorize Table" (TABLE)
// Validate Arguments
if TypeOf(TABLE) <> "array" then do
Throw("Table object must be an array.")
end
for i = 1 to TABLE.length do
colname = TABLE[i][1]
data = TABLE[i][2]
if TypeOf(data) = "array" then TABLE.(colname) = A2V(data)
end
return(TABLE)
EndMacro
/*
Like dply or SQL "select", returns a table with only
the columns listed in a_fields
TABLE: table object
a_fields: list of fields to select
*/
Macro "Select" (TABLE, a_fields)
NEWTABLE = null
for f = 1 to a_fields.length do
field = a_fields[f]
if !(TABLE.(field).length > 0) then
Throw("Select: " + field + " not found in table")
else NEWTABLE.(field) = TABLE.(field)
end
return(NEWTABLE)
EndMacro
/*
Similar to dplyr group_by() %>% summarize(), this macro groups a table
by specified columns and returns aggregate results. The stats are calculated
for all columns in the table that are not listed as grouping columns.
TABLE: A table object
a_groupFields: Array of column names to group by
agg:
Options array listing field and aggregation info
e.g. agg.weight = {"sum", "avg"}
This will sum and average the weight field
The possible aggregations are:
first, sum, high, low, avg, stddev
Returns
A table object of the summarized input table object
In the example above, the aggregated fields would be
sum_weight and avg_weight
*/
Macro "Summarize" (TABLE, a_groupFields, agg)
// Remove fields from TABLE that aren't listed for summary
for i = 1 to a_groupFields.length do
a_selected = a_selected + {a_groupFields[i]}
end
for i = 1 to agg.length do
a_selected = a_selected + {agg[i][1]}
end
TABLE = RunMacro("Select", TABLE, a_selected)
// Convert the TABLE object into a view in order
// to leverage GISDKs SelfAggregate() function
{view, fileName} = RunMacro("Table to View", TABLE)
// Create field specs for SelfAggregate()
agg_field_spec = view + "." + a_groupFields[1]
// Create the "Additional Groups" option for SelfAggregate()
opts = null
if a_groupFields.length > 1 then do
for g = 2 to a_groupFields.length do
opts.[Additional Groups] = opts.[Additional Groups] + {a_groupFields[g]}
end
end
// Create the fields option for SelfAggregate()
for i = 1 to agg.length do
name = agg[i][1]
stats = agg[i][2]
new_stats = null
for j = 1 to stats.length do
stat = stats[j]
new_stats = new_stats + {{Proper(stat)}}
end
fields.(name) = new_stats
end
opts.Fields = fields
// Create the new view using SelfAggregate()
agg_view = SelfAggregate("aggview", agg_field_spec, opts)
/*opts1.Fields = {{"vmt_change", {{"Sum"}, {"Avg"}}}}*/
/*agg_view = SelfAggregate("aggview", agg_field_spec, opts1)*/
// Read the view into a table object
TBL = RunMacro("View to Table", agg_view)
// The field names from SelfAggregate() are messy. Clean up.
// The first fields will be of the format "GroupedBy(ID)".
// Next is a "Count(bin)" field.
// Then there is a first field for each group variable ("First(ID)")
// Then the stat fields in the form of "Sum(trips)"
// Set group columns back to original name
for c = 1 to a_groupFields.length do
TBL[c][1] = a_groupFields[c]
end
// Set the count field name
TBL[a_groupFields.length + 1][1] = "Count"
// Remove the First() fields
TBL = ExcludeArrayElements(
TBL,
a_groupFields.length + 2,
a_groupFields.length
)
// Change fields like Sum(x) to sum_x
for i = 1 to agg.length do
field = agg[i][1]
stats = agg[i][2]
for j = 1 to stats.length do
stat = stats[j]
current_field = "[" + Proper(stat) + "(" + field + ")]"
new_field = lower(stat) + "_" + field
TBL = RunMacro("Rename Field", TBL, current_field, new_field)
end
end
CloseView(agg_view)
return(TBL)
EndMacro
/*
Creates a view based on a temporary binary file. The primary purpose of
this macro is to make GISDK functions/operations available for a table object.
The view is often read back into a table object afterwards.
TABLE: table object to convert to a view
Returns:
view_name: Name of the view as opened in TrandCAD
file_name: Name of the temporary file
*/
Macro "Table to View" (TABLE)
// Convert the TABLE object into a CSV and open the view
tempFile = GetTempFileName(".csv")
RunMacro("Write Table", TABLE, tempFile)
csv = OpenTable("csv", "CSV", {tempFile})
// Export to binary and open
file_name = GetTempFileName(".bin")
ExportView(csv + "|", "FFB", file_name, , )
view_name = OpenTable("bin", "FFB", {file_name})
// Clean up - even though temp files are deleted on program close,
// unsure what happens if the program crashes. Also, the CSV will generate
// a .DCC file when opened. Unsure if it gets deleted automatically, too.
CloseView(csv)
DeleteFile(tempFile)
DeleteFile(Substitute(tempFile, ".csv", ".DCC", ))
return({view_name, file_name})
EndMacro
/*
Returns an array of the column names of a table object
*/
Macro "Get Column Names" (TABLE)
for c = 1 to TABLE.length do
a_colnames = a_colnames + {TABLE[c][1]}
end
return(a_colnames)
EndMacro
/*
Applies a query to a table object.
TABLE: table object
query: Valid TransCAD query (e.g. "ID = 5" or "Name = 'Sam'")
*/
Macro "Filter Table" (TABLE, query)
{view, file} = RunMacro("Table to View", TABLE)
SetView(view)
query = "Select * where " + query
SelectByQuery("set", "Several", query)
TBL = RunMacro("View to Table", view, "set")
return(TBL)
EndMacro
/*
Changes the name of a column in a table object
TABLE: table object
current_name: current name of the field in the table
can be string or array of fields
new_name: desired new name of the field
can be string or array of fields
if array, must be the same length as current_name
*/
Macro "Rename Field" (TABLE, current_name, new_name)
// Argument checking
if TypeOf(current_name) <> TypeOf(new_name)
then Throw("Rename Field: current and new name must be same type")
if TypeOf(current_name) <> "string" then do
if current_name.lenth <> new_name.length
then Throw("Rename Field: Field name arrays must be same length")
end
// If a single field string, convert string to array
if TypeOf(current_name) = "string" then do
current_name = {current_name}
end
if TypeOf(new_name) = "string" then do
new_name = {new_name}
end
for n = 1 to current_name.length do
cName = current_name[n]
nName = new_name[n]
for c = 1 to TABLE.length do
if TABLE[c][1] = cName then TABLE[c][1] = nName
end
end
return(TABLE)
EndMacro
/*
Sets the names of a table. Unlike "Rename Field", it doesn't
matter what the current names are.
Inputs:
a_names
Array
Array of strings that are the field names. Must be the same
length as the table object.
Returns:
Table object with new names
*/
Macro "Set Table Names" (TABLE, a_names)
// Argument checking
if TABLE.length <> a_names.length
then Throw("Set Table Names: Argument 'a_names' must\n" +
"be the same length as table object."
)
NEWTABLE = TABLE
for c = 1 to TABLE.length do
NEWTABLE[c][1] = a_names[c]
end
return(NEWTABLE)
EndMacro
/*
Similar to dplyr's spread(). Unlike dplyr, a complete set of rows
must exist for each unique value of the key field. There is no
fill option for missing combinations.
TABLE: table object
key: field whose unique values will become column names
value: field whose data will populate the new columns
Returns a new table object
*/
Macro "Spread" (TABLE, key, value)
a_colnames = RunMacro("Get Column Names", TABLE)
if ArrayPosition(a_colnames, {key}, ) = 0
then Throw("Spread: key not found in table")
if ArrayPosition(a_colnames, {value}, ) = 0
then Throw("Spread: value not found in table")
opts = null
opts.Unique = "True"
a_unique = SortVector(TABLE.(key), opts)
for u = 1 to a_unique.length do
uniqueVal = a_unique[u]
if u = 1 then do
// Create new table
TBL = RunMacro(
"Filter Table", TABLE, key + " = '" + uniqueVal + "'"
)
TBL.(uniqueVal) = TBL.(value)
TBL.(key) = null
TBL.(value) = null
end else do
TEMP = RunMacro(
"Filter Table", TABLE, key + " = '" + uniqueVal + "'"
)
TBL.(uniqueVal) = TEMP.(value)
end
end
return(TBL)
EndMacro
/*
Joins two table objects.
master_tbl and slave_tbl
Table objects
m_id and s_id
String or array
The id fields from master and slave to use for join. Use an array to
specify multiple fields to join by.
Returns a table object.
*/
Macro "Join Tables" (master_tbl, m_id, slave_tbl, s_id)
{master_view, master_file} = RunMacro("Table to View", master_tbl)
{slave_view, slave_file} = RunMacro("Table to View", slave_tbl)
if TypeOf(m_id) = "string" then m_id = {m_id}
if TypeOf(s_id) = "string" then s_id = {s_id}
if m_id.length <> s_id.length then
Throw("Different number of fields used to join by")
dim m_spec[m_id.length]
dim s_spec[s_id.length]
for i = 1 to m_id.length do
m_spec[i] = master_view + "." + m_id[i]
s_spec[i] = slave_view + "." + s_id[i]
end
jv = JoinViewsMulti("jv", m_spec, s_spec, )
TABLE = RunMacro("View to Table", jv)
// JoinViewsMulti() will attach the view names to the m_id and s_id fields
// if they are the same.
// Remove the s_id fields, and clean the m_id fields (if needed)
for i = 1 to m_id.length do
m = m_id[i]
s = s_id[i]
if m = s then do
// Rename master field
current_name = "[" + master_view + "]." + m
TABLE = RunMacro("Rename Field", TABLE, current_name, m)
// Delete slave field
TABLE.("[" + slave_view + "]." + s) = null
end else do
// Delete slave field
TABLE.(s) = null
end
end
// Clean up the workspace
CloseView(jv)
CloseView(master_view)
DeleteFile(master_file)
DeleteFile(Substitute(master_file, ".bin", ".DCB", ))
CloseView(slave_view)
DeleteFile(slave_file)
DeleteFile(Substitute(slave_file, ".bin", ".DCB", ))
return(TABLE)
EndMacro
/*
Combines the rows of two tables. They must have the
same columns.
*/
Macro "Bind Rows" (first, second)
// Check that tables have same columns
col1 = RunMacro("Get Column Names", first)
col2 = RunMacro("Get Column Names", second)
for i = 1 to col1.length do
if col1[i] <> col2[i] then Throw("Bind Rows: Columns are not the same")
end
// Make sure both tables are vectorized
first = RunMacro("Vectorize Table", first)
second = RunMacro("Vectorize Table", second)
// Combine tables
final = null
for i = 1 to col1.length do
col_name = col1[i]
a1 = V2A(first.(col_name))
a2 = V2A(second.(col_name))
final.(col_name) = a1 + a2
end
// Vectorize the final table
final = RunMacro("Vectorize Table", final)
return(final)
EndMacro