First, for those who will be writing software, I highly suggest first checking out some of the Go documentation. The website is available at golang.org, and I would encourage going through the interactive tutorial and watching this video, which goes over some of the topics I'm about to review.
If you can, try to go through more of the Go tour than just the "Welcome" section. Methods and Interfaces may be particularly useful.
Compile and run the first example like so:
go build example1.go
./example1
The go
toolchain is available at unix.ucsc.edu.
The function json.NewDecoder
returns a decoder from the given file input,
which here is example1.json
. This decoder (dec
) then has a .Decode
method
which takes a pointer to a struct, hence the &person
.
Notice how the struct Person
is defined:
type Person struct {
Name string
Age int
}
When the decoder's Decode
method goes to do its work, it looks for fields in
the JSON that match the fields in the struct. This makes parsing JSON incredibly
easy.
Notice that the example1.json
file declares four seperate JSON objects. The
future GeoJSON examples we're going to look at will not be this way.
Notice that Abel
's Age
field is not set in the JSON. As a result, his age
value gets set to the zero value for int (0). Also notice that Dr. Frankenstein
has an extra field, Occupation
, which is ignored by the program.
Try changing the Name string
field to name string
, and see what happens.
Spoiler: Recall from the Go tutorials that Go only makes public keywords that
start with a capital letter. Making the name field lowercase makes it invisible
to the json
package, as name
is no longer exported.
Try changing the Name
field in the JSON to name
for any of the people. The
program will still work as expected. This is because the json
package fills
the structs case insensitevely.
Once again, this can be compiled and run like so:
go build example2.go
./example2
Take a second to look at the files and what they do first.
This example takes a list of coordinates in a JSON object and computes the magnitude of each. If you followed Go's method tutorial this will likely look very familiar.
The data
struct that is passed into the decoder has a "struct literal" type.
That basically means it doesn't have a name, but was just defined on the spot.
There was no real reason to do this, but it's possible and sometimes useful.
The json
package is smart enought to fill the coordinate array
([]Coordinate
) with the coordinate list in the JSON without any extra work -
all we had to do was specify it. This is pretty great for reading in our giant
arrays of intersections and stuff.
The last function in the file defines a method of the Coordinate
struct.
Notice that it recieves a pointer reciever. Try removing the *
and see if the
program still works. If you haven't already done the "Methods and Interfaces"
tour of Go, you probably should.
Anyway, the whole point is that this is pretty easy - we just defined a type,
defined a method that could operate on that type, and then the json
package
did all the heavy lifting for us and turned some text into useable data. Great!
Make sure you understand the for
and range
stuff. The use of Printf
's %v
notation is also a fun thing to be aware of.
This file is kind of long, so I tried to seperate the sections with large comments blocks.
This example shows a simple "simulation" which is entirely seperate from any specific format of data in JSON and how we can format some arbitrary JSON to be used in the simulation.
Take a look at the .go and .json and run it like the others.
The simulation is a simple struct with two methods: AddWidget
and Simulate
.
The simulation stores a list of widgets. These widgets are defined by the
SimulationWidget
. Any struct that defines a DoAThing() float64
method
satisfies the SimulationWidget
interface and can be put in the simulation.
The Person
struct stores a bunch of data that corresponds to fields in the
json. Notice that we've also defined a DoAThing() float64
method for the
Person
struct. That means we can put it in the simulation! Also take note that
DoAThing
method does not have to directly follow from the data, and that the
simulation won't care what that method is actually outputting. Try tinkering
with the DoAThing
function to return different values, or even adding new
fields to the JSON. Any type of input JSON is possible for this simulation, as
long as an appropriate DoAThing
method is defined.
The traffic simulation uses this strategy. At the time of writing, the model has
an AddEdge
method which recieves an Edge
interface defined as such:
type Edge interface {
Weight() float64
To() string
From() string
}
The Weight()
method is used to determine the cost of an agent travelling along
that edge (be it environmental cost, dollar cost, time cost, etc). In the
future, these edges may ask for other messages like AddAgent()
or
RemoveAgent()
(to respond to people congesting).
As such, we can import almost any kind of data and use it in the simulation, as long as we appropriately define some methods for the imported data.
I hope these examples have been illuminating about the specifics of data wrangling and how data can be imported into the simulation. The bottom line is we can accept almost any type of data as long as we implement a frontend for it.
In the next few days I'll be looking to put the simulation online, so we can all collaborate on interfacing for it, and then finishing up the simulation with agents et al.