Mapping the Bikeometers
I’ll map the locations of the Bikeometers to see where exactly they are in Arlington. To accomplish this, I found this video by Professor Lisa Lendway very helpful along with her website.
On the left are the numbers we need to enter into the bbox variable below in order to get the graph from Stamen Maps. I’ve chosen ‘terrain’ as my map type and the ‘zoom’ will be 13, which is in the openstreetmap url just after the text ‘map=’.
library(ggmap)
library(ggthemes)
# Get the map information
arlington <- get_stamenmap(
bbox = c(left = -77.2377, bottom = 38.8075, right = -76.9744, top = 38.9080),
maptype = "terrain",
zoom = 12)
Next we will import the Bikeometer data from my database that contains the longitude and latitudes needed to plot the Bikeometers.
library(DBI)
library(dplyr)
con <- dbConnect(odbc::odbc(), dsn = "Bike_MySQL")
sql_cmd <- "SELECT * FROM counts.bikeometer_details WHERE bikeometer_id in (14,15,16,18,22,31,39)"
# creates a lazy table
bikeometer_table <- dbGetQuery(con, sql_cmd)
bikeometer_table
One Bikeometer stands out as not even being in Alrington… how did that get in there? Let’s plot it anyway to see how close it is to Arlington. Realistically, I frequently bike into Alexandria on the weekends but I’ll probably end up excluding it none the less.
# Convert latitude and longitude to integers
bikeometer_table$longitude <- as.numeric(bikeometer_table$longitude)
bikeometer_table$latitude <- as.numeric(bikeometer_table$latitude)
# Plot the points on the map
ggmap(arlington) + # creates the map "background"
geom_point(data = bikeometer_table,
aes(x = longitude, y = latitude, color = region),
alpha = 0.5,
size = 3) +
theme_map()
From my experience, the most popular bike trail in Arlington will be on the Arlington Loop. I want to get a sense if these Bikeometers are on the Arlington Loop, and if not, choose Bikeometers that are on the loop. My hypothesis is that bike trails that weren’t popular before the pandemic won’t see much change after the pandemic. I generally either ride the Arlington Loop or the W&OD Trails as they are the most bike-friendly. A trail that is less bike-friendly before the pandemic won’t become that much more bike-friendly during a pandemic, even if there is a reduction of traffic in the city.
I’ll use the GPS coordinates from my Garmin GPS watch to create a layer for my plot that shows the Arlington Loop trail. I downloaded the gpx file from the Garmin Connect website.
I used the websites GPXStudio and MyGeodata Converter to remove unnecessary data points and convert from GPX to CSV respectively.
I read in the CSV which I’ll use to plot the Arlington Loop.
arlington_loop_table <- read.csv('~/Github/Arlington_Bikeometer_Visualizations/Data/arlington_loop.csv')
arlington_loop_table
I passed the ‘arlington’ map object to ggmap to set the background image to Arlington then plotted the Arlington Loop using geom_point. I used the ‘longitude’ column as the x-axis, ‘latitude’ column as the y-axis, ‘alpha’ is the transparency of the points (0 to 1) and ‘size’ is the size of each point. I used ‘theme_map’ without ‘x’ and ‘y’ axes.
ggmap(arlington) + # creates the map "background"
geom_point(data = arlington_loop_table,
aes(x = longitude, y = latitude),
alpha = 0.5,
size = .5) +
theme_map()
Next, I’ll create an Arlington Loop layer so I can overlay it on top of the Bikeometer location map. I found two options to create this layer: geom_polygon or geom_path. The biggest diffrence I see is that you can specify a fill for geom_polygon, but considering I don’t want to fill the layer in, I’ll use geom_path.
ggmap(arlington) + geom_polygon(aes(x = longitude, y = latitude),
data = arlington_loop_table, fill = 'blue', alpha = 0.1, size = 1, color = "red")
ggmap(arlington) + geom_path(data = arlington_loop_table, aes(x = longitude, y = latitude), size = 1, color = 'red') + theme_map()
If I wanted the path to change color according to elevation, I could use the below <color = ele> argument. This will generate a legend on the map. By adding the <theme(legend.background = element_blank())> code, you remove the background from the legend. However I won’t be using this code.
ggmap(arlington) + geom_path(data = arlington_loop_table, aes(x = longitude, y = latitude, color = ele), size = 1) + theme_map() + theme(legend.background = element_blank())
I’ll assign the geom_path to the arlington_loop_layer variable to be plotted on top of the plot with the location of the Bikeometes.
arlington_loop_layer <- geom_path(data = arlington_loop_table, aes(x = longitude, y = latitude), size = 1, color = 'red')
ggmap(arlington) + # creates the map "background"
geom_point(data = bikeometer_table,
aes(x = longitude, y = latitude, color = region),
alpha = 0.5,
size = 3) +
arlington_loop_layer +
theme_map()
It looks like 1 or 2 Bikeometers are on the trail. Let’s see which Bikeometers are actually on the Arlington Loop. I can use that data to pick some popular trails to visualize.
sql_cmd <- "SELECT * FROM counts.bikeometer_details"
# creates a lazy table
all_bikeometer_table <- dbGetQuery(con, sql_cmd)
all_bikeometer_table$longitude <- as.numeric(all_bikeometer_table$longitude)
all_bikeometer_table$latitude <- as.numeric(all_bikeometer_table$latitude)
all_bikeometer_table
To figure out labeling, I used this site.
ggmap(arlington) + # creates the map "background"
geom_point(data = all_bikeometer_table,
aes(x = longitude, y = latitude, color = region),
alpha = 0.5,
size = 10) +
arlington_loop_layer +
geom_text(data = all_bikeometer_table, nudge_x = 0.01, color='black', aes(x = longitude, y = latitude,
label = bikeometer_id, inherit.aes= FALSE)) +
theme_map()
## Warning: Ignoring unknown aesthetics: inherit.aes
## Warning: Removed 11 rows containing missing values (geom_point).
## Warning: Removed 11 rows containing missing values (geom_text).
Digression
For some reason, bikeometer_id 6 isn’t in my database. I’ll quickly convert my list of Bikeometers into integers so I can use the sorted() function in python to order them.
bikeometer_list = ['33','30','43','24','59','56','47','48','10','20',
'35','57','18','3','58','61','62','38','44','14',
'60','5','6','42','37','27','26','8','7','51','52',
'45','22','21','36','34','41','9','39','16','15',
'54','55','31','28','11','2','25','19']
# Convert to integers
bikeometer_list = [int(i) for i in bikeometer_list]
sorted_bikeometer_list = sorted(bikeometer_list)
sorted_bikeometer_list
## [2, 3, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 33, 34, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 48, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62]
With them ordered, I’ll check to see which Bikeometers are in my database.
It looks like Bikeometers 6 and 52 are not in my database. When I request data from them for a few different time ranges, it looks like there is no data for either of them, which explains why they aren’t in my database. One mystery solved!
Digression Over
Right now, the bikeometer_id labels are ugly. If we don’t use the ‘nudge’ parameter, it makes some a little more readable and some a lot less readable. My next goal is to make the Bikeometer locations easier to read and see so I can pick the ones that are on the Arlington Loop. Ideally, each point would have an arrow pointing to its location with a label at the other end of the arrow. I wonder if there is a package that can make something like this…
LS0tDQp0aXRsZTogIlZpc3VhbGl6aW5nIEFybGluZ3RvbiBCaWtvbWV0ZXJzIg0Kc3VidGl0bGU6ICJQYXJ0IDc6IEludmVzdGlnYXRpbmcgQmlrZW9tZXRlciBHUFMgTG9jYXRpb25zIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMg0KICAgIHRvY19mbG9hdDogeWVzDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoZWFkZXIuaHRtbA0KLS0tDQoNCsKgIMKgDQoNCiMgR29hbA0KDQpDb21wYXJlIHRoZSBHUFMgZGF0YSBvZiB0aGUgY2hvc2VuIEJpa2VvbWV0ZXJzIHRvIHRoZSBwb3B1bGFyIHRyYWlsOiBBcmxpbmd0b24gTG9vcC4NCg0KIyBNYXBwaW5nIHRoZSBCaWtlb21ldGVycw0KDQpJJ2xsIG1hcCB0aGUgbG9jYXRpb25zIG9mIHRoZSBCaWtlb21ldGVycyB0byBzZWUgd2hlcmUgZXhhY3RseSB0aGV5IGFyZSBpbiBBcmxpbmd0b24uIFRvIGFjY29tcGxpc2ggdGhpcywgSSBmb3VuZCBbdGhpcyB2aWRlb10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj0yazhPLVlfdWlSVSZ0PTNzKSBieSBQcm9mZXNzb3IgTGlzYSBMZW5kd2F5IHZlcnkgaGVscGZ1bCBhbG9uZyB3aXRoIFtoZXIgd2Vic2l0ZV0oaHR0cHM6Ly9tYXBwaW5nLWluLXIubmV0bGlmeS5hcHAvKS4NCg0KIVtdKGltYWdlcy9Bcmxpbmd0b24lMjBPcGVuJTIwU3RyZWV0JTIwTWFwLnBuZyAiQXJsaW5ndG9uIE9wZW4gU3RyZWV0IE1hcCBiYm94IikNCg0KT24gdGhlIGxlZnQgYXJlIHRoZSBudW1iZXJzIHdlIG5lZWQgdG8gZW50ZXIgaW50byB0aGUgYmJveCB2YXJpYWJsZSBiZWxvdyBpbiBvcmRlciB0byBnZXQgdGhlIGdyYXBoIGZyb20gW1N0YW1lbiBNYXBzXShodHRwOi8vbWFwcy5zdGFtZW4uY29tLykuIEkndmUgY2hvc2VuICd0ZXJyYWluJyBhcyBteSBtYXAgdHlwZSBhbmQgdGhlICd6b29tJyB3aWxsIGJlIDEzLCB3aGljaCBpcyBpbiB0aGUgb3BlbnN0cmVldG1hcCB1cmwganVzdCBhZnRlciB0aGUgdGV4dCAnbWFwPScuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGdnbWFwKQ0KbGlicmFyeShnZ3RoZW1lcykNCiMgR2V0IHRoZSBtYXAgaW5mb3JtYXRpb24NCmFybGluZ3RvbiA8LSBnZXRfc3RhbWVubWFwKA0KICAgIGJib3ggPSBjKGxlZnQgPSAtNzcuMjM3NywgYm90dG9tID0gMzguODA3NSwgcmlnaHQgPSAtNzYuOTc0NCwgdG9wID0gMzguOTA4MCksIA0KICAgIG1hcHR5cGUgPSAidGVycmFpbiIsDQogICAgem9vbSA9IDEyKQ0KYGBgDQoNCk5leHQgd2Ugd2lsbCBpbXBvcnQgdGhlIEJpa2VvbWV0ZXIgZGF0YSBmcm9tIG15IGRhdGFiYXNlIHRoYXQgY29udGFpbnMgdGhlIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGVzIG5lZWRlZCB0byBwbG90IHRoZSBCaWtlb21ldGVycy4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQpsaWJyYXJ5KERCSSkNCmxpYnJhcnkoZHBseXIpDQpjb24gPC0gZGJDb25uZWN0KG9kYmM6Om9kYmMoKSwgZHNuID0gIkJpa2VfTXlTUUwiKQ0Kc3FsX2NtZCA8LSAiU0VMRUNUICogRlJPTSBjb3VudHMuYmlrZW9tZXRlcl9kZXRhaWxzIFdIRVJFIGJpa2VvbWV0ZXJfaWQgaW4gKDE0LDE1LDE2LDE4LDIyLDMxLDM5KSINCiMgY3JlYXRlcyBhIGxhenkgdGFibGUNCmJpa2VvbWV0ZXJfdGFibGUgPC0gZGJHZXRRdWVyeShjb24sIHNxbF9jbWQpDQpiaWtlb21ldGVyX3RhYmxlDQpgYGANCg0KT25lIEJpa2VvbWV0ZXIgc3RhbmRzIG91dCBhcyBub3QgZXZlbiBiZWluZyBpbiBBbHJpbmd0b24uLi4gaG93IGRpZCB0aGF0IGdldCBpbiB0aGVyZT8gTGV0J3MgcGxvdCBpdCBhbnl3YXkgdG8gc2VlIGhvdyBjbG9zZSBpdCBpcyB0byBBcmxpbmd0b24uIFJlYWxpc3RpY2FsbHksIEkgZnJlcXVlbnRseSBiaWtlIGludG8gQWxleGFuZHJpYSBvbiB0aGUgd2Vla2VuZHMgYnV0IEknbGwgcHJvYmFibHkgZW5kIHVwIGV4Y2x1ZGluZyBpdCBub25lIHRoZSBsZXNzLg0KDQpgYGB7cn0NCiMgQ29udmVydCBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIHRvIGludGVnZXJzDQpiaWtlb21ldGVyX3RhYmxlJGxvbmdpdHVkZSA8LSBhcy5udW1lcmljKGJpa2VvbWV0ZXJfdGFibGUkbG9uZ2l0dWRlKQ0KYmlrZW9tZXRlcl90YWJsZSRsYXRpdHVkZSA8LSBhcy5udW1lcmljKGJpa2VvbWV0ZXJfdGFibGUkbGF0aXR1ZGUpDQoNCiMgUGxvdCB0aGUgcG9pbnRzIG9uIHRoZSBtYXANCmdnbWFwKGFybGluZ3RvbikgKyAjIGNyZWF0ZXMgdGhlIG1hcCAiYmFja2dyb3VuZCINCiAgZ2VvbV9wb2ludChkYXRhID0gYmlrZW9tZXRlcl90YWJsZSwgDQogICAgICAgICAgICAgYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSwgY29sb3IgPSByZWdpb24pLCANCiAgICAgICAgICAgICBhbHBoYSA9IDAuNSwgDQogICAgICAgICAgICAgc2l6ZSA9IDMpICsNCiAgICAgICAgICAgICB0aGVtZV9tYXAoKQ0KYGBgDQoNCkZyb20gbXkgZXhwZXJpZW5jZSwgdGhlIG1vc3QgcG9wdWxhciBiaWtlIHRyYWlsIGluIEFybGluZ3RvbiB3aWxsIGJlIG9uIHRoZSBBcmxpbmd0b24gTG9vcC4gSSB3YW50IHRvIGdldCBhIHNlbnNlIGlmIHRoZXNlIEJpa2VvbWV0ZXJzIGFyZSBvbiB0aGUgQXJsaW5ndG9uIExvb3AsIGFuZCBpZiBub3QsIGNob29zZSBCaWtlb21ldGVycyB0aGF0IGFyZSBvbiB0aGUgbG9vcC4gTXkgaHlwb3RoZXNpcyBpcyB0aGF0IGJpa2UgdHJhaWxzIHRoYXQgd2VyZW4ndCBwb3B1bGFyIGJlZm9yZSB0aGUgcGFuZGVtaWMgd29uJ3Qgc2VlIG11Y2ggY2hhbmdlIGFmdGVyIHRoZSBwYW5kZW1pYy4gSSBnZW5lcmFsbHkgZWl0aGVyIHJpZGUgdGhlIEFybGluZ3RvbiBMb29wIG9yIHRoZSBXJk9EIFRyYWlscyBhcyB0aGV5IGFyZSB0aGUgbW9zdCBiaWtlLWZyaWVuZGx5LiBBIHRyYWlsIHRoYXQgaXMgbGVzcyBiaWtlLWZyaWVuZGx5IGJlZm9yZSB0aGUgcGFuZGVtaWMgd29uJ3QgYmVjb21lIHRoYXQgbXVjaCBtb3JlIGJpa2UtZnJpZW5kbHkgZHVyaW5nIGEgcGFuZGVtaWMsIGV2ZW4gaWYgdGhlcmUgaXMgYSByZWR1Y3Rpb24gb2YgdHJhZmZpYyBpbiB0aGUgY2l0eS4NCg0KSSdsbCB1c2UgdGhlIEdQUyBjb29yZGluYXRlcyBmcm9tIG15IEdhcm1pbiBHUFMgd2F0Y2ggdG8gY3JlYXRlIGEgbGF5ZXIgZm9yIG15IHBsb3QgdGhhdCBzaG93cyB0aGUgQXJsaW5ndG9uIExvb3AgdHJhaWwuIEkgZG93bmxvYWRlZCB0aGUgZ3B4IGZpbGUgZnJvbSB0aGUgR2FybWluIENvbm5lY3Qgd2Vic2l0ZS4NCg0KIVtdKGltYWdlcy9HUFglMjBmaWxlJTIwZnJvbSUyMEdhcm1pbiUyMENvbm5lY3QlMjBXZWJzaXRlLmpwZyAiR2FybWluIENvbm5lY3QgR1BYIikNCg0KSSB1c2VkIHRoZSB3ZWJzaXRlcyBbR1BYU3R1ZGlvXShodHRwczovL2dweHN0dWRpby5naXRodWIuaW8vKSBhbmQgW015R2VvZGF0YSBDb252ZXJ0ZXJdKGh0dHBzOi8vbXlnZW9kYXRhLmNsb3VkL2NvbnZlcnRlci9ncHgtdG8tY3N2KSB0byByZW1vdmUgdW5uZWNlc3NhcnkgZGF0YSBwb2ludHMgYW5kIGNvbnZlcnQgZnJvbSBHUFggdG8gQ1NWIHJlc3BlY3RpdmVseS4NCg0KSSByZWFkIGluIHRoZSBDU1Ygd2hpY2ggSSdsbCB1c2UgdG8gcGxvdCB0aGUgQXJsaW5ndG9uIExvb3AuDQoNCmBgYHtyfQ0KYXJsaW5ndG9uX2xvb3BfdGFibGUgPC0gcmVhZC5jc3YoJ34vR2l0aHViL0FybGluZ3Rvbl9CaWtlb21ldGVyX1Zpc3VhbGl6YXRpb25zL0RhdGEvYXJsaW5ndG9uX2xvb3AuY3N2JykNCmFybGluZ3Rvbl9sb29wX3RhYmxlDQpgYGANCg0KSSBwYXNzZWQgdGhlICdhcmxpbmd0b24nIG1hcCBvYmplY3QgdG8gZ2dtYXAgdG8gc2V0IHRoZSBiYWNrZ3JvdW5kIGltYWdlIHRvIEFybGluZ3RvbiB0aGVuIHBsb3R0ZWQgdGhlIEFybGluZ3RvbiBMb29wIHVzaW5nIGdlb21fcG9pbnQuIEkgdXNlZCB0aGUgJ2xvbmdpdHVkZScgY29sdW1uIGFzIHRoZSB4LWF4aXMsICdsYXRpdHVkZScgY29sdW1uIGFzIHRoZSB5LWF4aXMsICdhbHBoYScgaXMgdGhlIHRyYW5zcGFyZW5jeSBvZiB0aGUgcG9pbnRzICgwIHRvIDEpIGFuZCAnc2l6ZScgaXMgdGhlIHNpemUgb2YgZWFjaCBwb2ludC4gSSB1c2VkICd0aGVtZV9tYXAnIHdpdGhvdXQgJ3gnIGFuZCAneScgYXhlcy4NCg0KYGBge3J9DQpnZ21hcChhcmxpbmd0b24pICsgIyBjcmVhdGVzIHRoZSBtYXAgImJhY2tncm91bmQiDQogIGdlb21fcG9pbnQoZGF0YSA9IGFybGluZ3Rvbl9sb29wX3RhYmxlLCANCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlKSwgDQogICAgICAgICAgICAgYWxwaGEgPSAwLjUsIA0KICAgICAgICAgICAgIHNpemUgPSAuNSkgKw0KICAgICAgICAgICAgIHRoZW1lX21hcCgpDQpgYGANCg0KTmV4dCwgSSdsbCBjcmVhdGUgYW4gQXJsaW5ndG9uIExvb3AgbGF5ZXIgc28gSSBjYW4gb3ZlcmxheSBpdCBvbiB0b3Agb2YgdGhlIEJpa2VvbWV0ZXIgbG9jYXRpb24gbWFwLiBJIGZvdW5kIHR3byBvcHRpb25zIHRvIGNyZWF0ZSB0aGlzIGxheWVyOiBnZW9tX3BvbHlnb24gb3IgZ2VvbV9wYXRoLiBUaGUgYmlnZ2VzdCBkaWZmcmVuY2UgSSBzZWUgaXMgdGhhdCB5b3UgY2FuIHNwZWNpZnkgYSBmaWxsIGZvciBnZW9tX3BvbHlnb24sIGJ1dCBjb25zaWRlcmluZyBJIGRvbid0IHdhbnQgdG8gZmlsbCB0aGUgbGF5ZXIgaW4sIEknbGwgdXNlIGdlb21fcGF0aC4NCg0KYGBge3J9DQpnZ21hcChhcmxpbmd0b24pICsgZ2VvbV9wb2x5Z29uKGFlcyh4ID0gbG9uZ2l0dWRlLCB5ID0gbGF0aXR1ZGUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gYXJsaW5ndG9uX2xvb3BfdGFibGUsIGZpbGwgPSAnYmx1ZScsIGFscGhhID0gMC4xLCBzaXplID0gMSwgY29sb3IgPSAicmVkIikNCmBgYA0KDQpgYGB7cn0NCmdnbWFwKGFybGluZ3RvbikgKyBnZW9tX3BhdGgoZGF0YSA9IGFybGluZ3Rvbl9sb29wX3RhYmxlLCBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlKSwgc2l6ZSA9IDEsIGNvbG9yID0gJ3JlZCcpICsgdGhlbWVfbWFwKCkNCg0KYGBgDQoNCklmIEkgd2FudGVkIHRoZSBwYXRoIHRvIGNoYW5nZSBjb2xvciBhY2NvcmRpbmcgdG8gZWxldmF0aW9uLCBJIGNvdWxkIHVzZSB0aGUgYmVsb3cgXDxjb2xvciA9IGVsZVw+IGFyZ3VtZW50LiBUaGlzIHdpbGwgZ2VuZXJhdGUgYSBsZWdlbmQgb24gdGhlIG1hcC4gQnkgYWRkaW5nIHRoZSBcPHRoZW1lKGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKVw+IGNvZGUsIHlvdSByZW1vdmUgdGhlIGJhY2tncm91bmQgZnJvbSB0aGUgbGVnZW5kLiBIb3dldmVyIEkgd29uJ3QgYmUgdXNpbmcgdGhpcyBjb2RlLg0KDQpgYGB7cn0NCmdnbWFwKGFybGluZ3RvbikgKyBnZW9tX3BhdGgoZGF0YSA9IGFybGluZ3Rvbl9sb29wX3RhYmxlLCBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBjb2xvciA9IGVsZSksIHNpemUgPSAxKSArIHRoZW1lX21hcCgpICsgdGhlbWUobGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KSSdsbCBhc3NpZ24gdGhlIGdlb21fcGF0aCB0byB0aGUgYXJsaW5ndG9uX2xvb3BfbGF5ZXIgdmFyaWFibGUgdG8gYmUgcGxvdHRlZCBvbiB0b3Agb2YgdGhlIHBsb3Qgd2l0aCB0aGUgbG9jYXRpb24gb2YgdGhlIEJpa2VvbWV0ZXMuDQoNCmBgYHtyfQ0KYXJsaW5ndG9uX2xvb3BfbGF5ZXIgPC0gZ2VvbV9wYXRoKGRhdGEgPSBhcmxpbmd0b25fbG9vcF90YWJsZSwgYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSksIHNpemUgPSAxLCBjb2xvciA9ICdyZWQnKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dtYXAoYXJsaW5ndG9uKSArICMgY3JlYXRlcyB0aGUgbWFwICJiYWNrZ3JvdW5kIg0KICBnZW9tX3BvaW50KGRhdGEgPSBiaWtlb21ldGVyX3RhYmxlLCANCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBjb2xvciA9IHJlZ2lvbiksIA0KICAgICAgICAgICAgIGFscGhhID0gMC41LCANCiAgICAgICAgICAgICBzaXplID0gMykgKw0KICAgICAgICAgICAgICBhcmxpbmd0b25fbG9vcF9sYXllciArDQogICAgICAgICAgICAgdGhlbWVfbWFwKCkNCmBgYA0KDQpJdCBsb29rcyBsaWtlIDEgb3IgMiBCaWtlb21ldGVycyBhcmUgb24gdGhlIHRyYWlsLiBMZXQncyBzZWUgd2hpY2ggQmlrZW9tZXRlcnMgYXJlIGFjdHVhbGx5IG9uIHRoZSBBcmxpbmd0b24gTG9vcC4gSSBjYW4gdXNlIHRoYXQgZGF0YSB0byBwaWNrIHNvbWUgcG9wdWxhciB0cmFpbHMgdG8gdmlzdWFsaXplLg0KDQpgYGB7cn0NCnNxbF9jbWQgPC0gIlNFTEVDVCAqIEZST00gY291bnRzLmJpa2VvbWV0ZXJfZGV0YWlscyINCiMgY3JlYXRlcyBhIGxhenkgdGFibGUNCmFsbF9iaWtlb21ldGVyX3RhYmxlIDwtIGRiR2V0UXVlcnkoY29uLCBzcWxfY21kKQ0KYWxsX2Jpa2VvbWV0ZXJfdGFibGUkbG9uZ2l0dWRlIDwtIGFzLm51bWVyaWMoYWxsX2Jpa2VvbWV0ZXJfdGFibGUkbG9uZ2l0dWRlKQ0KYWxsX2Jpa2VvbWV0ZXJfdGFibGUkbGF0aXR1ZGUgPC0gYXMubnVtZXJpYyhhbGxfYmlrZW9tZXRlcl90YWJsZSRsYXRpdHVkZSkNCmFsbF9iaWtlb21ldGVyX3RhYmxlDQpgYGANCg0KVG8gZmlndXJlIG91dCBsYWJlbGluZywgSSB1c2VkIFt0aGlzIHNpdGVdKGh0dHBzOi8vcmFmYWxhYi5naXRodWIuaW8vZHNib29rL2dncGxvdDIuaHRtbCkuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpnZ21hcChhcmxpbmd0b24pICsgIyBjcmVhdGVzIHRoZSBtYXAgImJhY2tncm91bmQiDQogIGdlb21fcG9pbnQoZGF0YSA9IGFsbF9iaWtlb21ldGVyX3RhYmxlLCANCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBjb2xvciA9IHJlZ2lvbiksIA0KICAgICAgICAgICAgIGFscGhhID0gMC41LCANCiAgICAgICAgICAgICBzaXplID0gMTApICsNCiAgYXJsaW5ndG9uX2xvb3BfbGF5ZXIgKw0KICBnZW9tX3RleHQoZGF0YSA9IGFsbF9iaWtlb21ldGVyX3RhYmxlLCBudWRnZV94ID0gMC4wMSwgY29sb3I9J2JsYWNrJywgYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYmlrZW9tZXRlcl9pZCwgaW5oZXJpdC5hZXM9IEZBTFNFKSkgKw0KICB0aGVtZV9tYXAoKQ0KYGBgDQoNCiMjIERpZ3Jlc3Npb24NCg0KRm9yIHNvbWUgcmVhc29uLCBiaWtlb21ldGVyX2lkIDYgaXNuJ3QgaW4gbXkgZGF0YWJhc2UuIEknbGwgcXVpY2tseSBjb252ZXJ0IG15IGxpc3Qgb2YgQmlrZW9tZXRlcnMgaW50byBpbnRlZ2VycyBzbyBJIGNhbiB1c2UgdGhlIHNvcnRlZCgpIGZ1bmN0aW9uIGluIHB5dGhvbiB0byBvcmRlciB0aGVtLg0KDQpgYGB7ciBpbmNsdWRlPUZBTFNFLCBjYWNoZT1UUlVFfQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KYGBgDQoNCmBgYHtweXRob259DQpiaWtlb21ldGVyX2xpc3QgPSBbJzMzJywnMzAnLCc0MycsJzI0JywnNTknLCc1NicsJzQ3JywnNDgnLCcxMCcsJzIwJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICczNScsJzU3JywnMTgnLCczJywnNTgnLCc2MScsJzYyJywnMzgnLCc0NCcsJzE0JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICc2MCcsJzUnLCc2JywnNDInLCczNycsJzI3JywnMjYnLCc4JywnNycsJzUxJywnNTInLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzQ1JywnMjInLCcyMScsJzM2JywnMzQnLCc0MScsJzknLCczOScsJzE2JywnMTUnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzU0JywnNTUnLCczMScsJzI4JywnMTEnLCcyJywnMjUnLCcxOSddDQoNCiMgQ29udmVydCB0byBpbnRlZ2Vycw0KYmlrZW9tZXRlcl9saXN0ID0gW2ludChpKSBmb3IgaSBpbiBiaWtlb21ldGVyX2xpc3RdDQpzb3J0ZWRfYmlrZW9tZXRlcl9saXN0ID0gc29ydGVkKGJpa2VvbWV0ZXJfbGlzdCkNCnNvcnRlZF9iaWtlb21ldGVyX2xpc3QNCmBgYA0KDQpXaXRoIHRoZW0gb3JkZXJlZCwgSSdsbCBjaGVjayB0byBzZWUgd2hpY2ggQmlrZW9tZXRlcnMgYXJlIGluIG15IGRhdGFiYXNlLg0KDQpJdCBsb29rcyBsaWtlIEJpa2VvbWV0ZXJzIDYgYW5kIDUyIGFyZSBub3QgaW4gbXkgZGF0YWJhc2UuIFdoZW4gSSByZXF1ZXN0IGRhdGEgZnJvbSB0aGVtIGZvciBhIGZldyBkaWZmZXJlbnQgdGltZSByYW5nZXMsIGl0IGxvb2tzIGxpa2UgdGhlcmUgaXMgbm8gZGF0YSBmb3IgZWl0aGVyIG9mIHRoZW0sIHdoaWNoIGV4cGxhaW5zIHdoeSB0aGV5IGFyZW4ndCBpbiBteSBkYXRhYmFzZS4gT25lIG15c3Rlcnkgc29sdmVkIQ0KDQojIyBEaWdyZXNzaW9uIE92ZXINCg0KUmlnaHQgbm93LCB0aGUgYmlrZW9tZXRlcl9pZCBsYWJlbHMgYXJlIHVnbHkuIElmIHdlIGRvbid0IHVzZSB0aGUgJ251ZGdlJyBwYXJhbWV0ZXIsIGl0IG1ha2VzIHNvbWUgYSBsaXR0bGUgbW9yZSByZWFkYWJsZSBhbmQgc29tZSBhIGxvdCBsZXNzIHJlYWRhYmxlLiBNeSBuZXh0IGdvYWwgaXMgdG8gbWFrZSB0aGUgQmlrZW9tZXRlciBsb2NhdGlvbnMgZWFzaWVyIHRvIHJlYWQgYW5kIHNlZSBzbyBJIGNhbiBwaWNrIHRoZSBvbmVzIHRoYXQgYXJlIG9uIHRoZSBBcmxpbmd0b24gTG9vcC4gSWRlYWxseSwgZWFjaCBwb2ludCB3b3VsZCBoYXZlIGFuIGFycm93IHBvaW50aW5nIHRvIGl0cyBsb2NhdGlvbiB3aXRoIGEgbGFiZWwgYXQgdGhlIG90aGVyIGVuZCBvZiB0aGUgYXJyb3cuIEkgd29uZGVyIGlmIHRoZXJlIGlzIGEgcGFja2FnZSB0aGF0IGNhbiBtYWtlIHNvbWV0aGluZyBsaWtlIHRoaXMuLi4NCg==