Visualise Twitter activity using a procedurally-generated 3D city model
Esri's CityEngine is a 3D GIS tool which allows city models to be generated procedurally (that is, models are generated using a set of rules). This approach allows us to define simple conditions which govern the appearance of a city model.
Twitter provides a 'firehose' of real-time tweets, including their location if this functionality has been enabled by a user.
Using CityEngine's Python scripting functionality, we've created a model of London in which buildings grow upwards, according to the frequency of tweets which are sent from (or in close proximity to) them.
This script requires the Tweepy library. You should first install the pip tool for your Python installation. Because CityEngine uses its own Jython installation, it's easiest to install Tweepy into a specific target directory external to that installation:
mkdir /path/to/directory
pip install -t /path/to/directory tweepy
This directory should be added to your Python path using sys.path.append("C:/path/to/directory")
before you import tweepy in your script. See our example here.
You may use any spatial basemap you like. In this script, the co-ordinates cover the area of wider London, however, they can be altered by the user in the script.
In order to run the demo:
tileslondon.shp
, cityengine_twitter.py
and samplerulefile.cga
in CityEnginesamplerulefile.cga
in tileslondon
, in case it is not assigned alreadyIf you wish to use your own basemap, the steps are as follows:
dxf
&c.), cityengine_twitter.py
and samplerulefile.cga
into CityEngine (The script is created for the area of London, so make sure the coordinate system is “British National Grid”). If you are visualising a city outside the UK, this is not necessary, but you will also have to modify the Python script to skip the conversion step to BNG in the on_status methodbuildings_tq_small
(this corresponds to the variable value here)samplerulefile.cga
to the CityEngine layer in which you wish to collect the tweets. In the rule options of the inspector use Lot
as Start Rule. You must be able to see three attributes under samplerulefile
:
HGT
(controls the height of shapes (object defined))Opacityshape
(controls the opacity of the shapes without tweets (user controlled)),Opacitytwit
(controls the opacity of the shapes with tweets (user controlled)).HGT
count_t
maxHGT
twitHGT
maxdistance
HGT
, select source for attribute HGT
, and set it to the Object attribute with the name HGT
.HGT
and count_t
variables to 0
Using Tweepy, we have defined a bounding box around Greater London. When a tweet is sent to our script by the Twitter streaming API, we determine whether it contains GPS data, and discard it if not.
The basemap we're using is a map of London building footprints, which is supplied by Ordnance Survey, and which can be obtained from Digimap. In order to place a tweet on our map, we must first convert its coordinates from WGS latitude and longitude points to Ordnance Survey National Grid coordinates.
Following the conversion we determine whether the tweet falls within the boundary of a building on our basemap. This calculation is performed in two steps:
x
and y
coordinates of its vertices, and apply the even-odd rule to determine whether the point falls within the surface defined by the vertices. If it does, we extrude the shape by a given factor, set its colour, and define certain characteristics. In our case, we are mapping one particular attribute – location – to the shape in the form of height. However, any attribute of the tweet could be used to alter a shape's attributes. For instance, we could set the heights of the buildings to their true heights, but add a window to the buildings each time a tweet is receivedBecause we're mapping tweet frequency to building height, we have had to implement a method of slowing the growth of building heights, in order to avoid quickly growing locations which generate a lot of tweets (e.g. the British Museum):
This equation scales the shape's height,h
, by an amount which decreases linearly as it grows towards the maximum height.
An example generator which yields these values:
def height():
"""
Yield linearly-decreasing values, beginning with 100
"""
maxheight = 18000.00
previous = 0.00
while True:
newheight = previous + (((maxheight - previous) / maxheight) * 100.00)
previous = newheight
yield newheight
scaled_height = height()
for h in xrange(5):
print(scaled_height.next())
# 100.00, 199.44, 298.33, 396.67, 494.47
If you make use of this work, please cite it using the following DOI: