Introduction
This assignment consists of three main sections.
In the first section, I selected census tracts that in my opinion are walkable and unwalkable within Fulton and DeKalb Counties, GA.
The second section is the main part of this assignment in which I prepared OSM data, download GSV images, apply computer vision technique I’ve learned in the class (i.e., semantic segmentation).
In the third section, is the summary and analysis of the output and my findings. I apply computer vision to segment the pixels in the images and calculated proportion of objects focusing on the following categories : building, sky, tree, road, and sidewalk. The results are visualized using (1) interactive maps to visualize the spatial distribution of different objects, (2) bar chart to compare the mean of each category between the two areas and (3) the boxplots to compare the distributions.
library(tidyverse)
library(tidycensus)
library(osmdata)
library(sfnetworks)
library(units)
library(sf)
library(tidygraph)
library(tmap)
library(here)
Section 1. Choose Census Tracts.
Based on my experience walking around the neighborhood near the Georgia Tech campus and my home, I feel that the Tech Square area (GEOID: 13121001205 and 13121001206) is much more convenient for walking than other parts. For areas with poor walkability, I’d highlight the northwest side of campus (GEOID: 13121000601 and 13121000602), as I live nearby and occasionally walk to the mall and IKEA. Some sections lack sidewalks, and there are broken traffic lights, making walking difficult.
Step 1. Get OSM data and clean it.
The getbb()
function, which we used in the class to
download OSM data, isn’t suitable for downloading just two Census
Tracts. We will instead use an alternative method.
- Using tidycensus package, download the Census Tract polygon for Fulton and DeKalb counties.
- Extract two Census Tracts, each of which will be your most walkable and least walkable Census Tracts.
- Using their bounding boxes, get OSM data.
- Convert them into sfnetwork object and clean it.
# TASK ////////////////////////////////////////////////////////////////////////
# 1. Set up your api key here
census_api_key(
Sys.getenv("CENSUS_API_KEY")
)
## To install your API key for use in future sessions, run this function with `install = TRUE`.
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Download Census Tract polygon for Fulton and DeKalb
tract <- get_acs("tract",
variables = c('tot_pop' = 'B01001_001'),
year = 2022,
state = "GA",
county = c("Fulton", "DeKalb"),
geometry = TRUE)
## Getting data from the 2018-2022 5-year ACS
## Downloading feature geometry from the Census website. To cache shapefiles for use in future sessions, set `options(tigris_use_cache = TRUE)`.
## | | | 0% | |= | 1% | |== | 2% | |== | 3% | |=== | 4% | |==== | 5% | |==== | 6% | |===== | 7% | |====== | 8% | |====== | 9% | |======= | 10% | |======== | 11% | |======== | 12% | |========= | 12% | |========= | 13% | |========== | 14% | |========== | 15% | |=========== | 15% | |=========== | 16% | |============ | 17% | |============ | 18% | |============= | 18% | |============= | 19% | |============== | 20% | |============== | 21% | |=============== | 21% | |=============== | 22% | |================ | 23% | |================= | 24% | |================= | 25% | |================== | 26% | |=================== | 26% | |=================== | 27% | |==================== | 28% | |==================== | 29% | |===================== | 29% | |===================== | 30% | |====================== | 31% | |====================== | 32% | |======================= | 32% | |======================= | 33% | |======================== | 34% | |======================== | 35% | |========================= | 35% | |========================= | 36% | |========================== | 37% | |========================== | 38% | |=========================== | 38% | |=========================== | 39% | |============================ | 40% | |============================= | 41% | |============================= | 42% | |============================== | 43% | |=============================== | 44% | |=============================== | 45% | |================================ | 46% | |================================= | 47% | |================================= | 48% | |================================== | 49% | |=================================== | 49% | |=================================== | 50% | |==================================== | 51% | |==================================== | 52% | |===================================== | 52% | |===================================== | 53% | |====================================== | 54% | |======================================= | 55% | |======================================= | 56% | |======================================== | 57% | |========================================= | 58% | |========================================= | 59% | |========================================== | 60% | |=========================================== | 61% | |=========================================== | 62% | |============================================ | 63% | |============================================= | 64% | |============================================= | 65% | |============================================== | 66% | |=============================================== | 67% | |=============================================== | 68% | |================================================ | 69% | |================================================= | 70% | |================================================= | 71% | |================================================== | 71% | |=================================================== | 72% | |=================================================== | 73% | |==================================================== | 74% | |===================================================== | 75% | |===================================================== | 76% | |====================================================== | 77% | |======================================================= | 78% | |======================================================= | 79% | |======================================================== | 80% | |========================================================= | 81% | |========================================================= | 82% | |========================================================== | 82% | |========================================================== | 83% | |=========================================================== | 84% | |=========================================================== | 85% | |============================================================ | 85% | |============================================================ | 86% | |============================================================= | 87% | |============================================================= | 88% | |============================================================== | 88% | |============================================================== | 89% | |=============================================================== | 90% | |=============================================================== | 91% | |================================================================ | 91% | |================================================================ | 92% | |================================================================= | 93% | |================================================================== | 94% | |=================================================================== | 95% | |=================================================================== | 96% | |==================================================================== | 97% | |===================================================================== | 98% | |===================================================================== | 99% | |======================================================================| 99% | |======================================================================| 100%
# =========== NO MODIFY ZONE ENDS HERE ========================================
# TASK ////////////////////////////////////////////////////////////////////////
# The purpose of this TASK is to create one bounding box for walkable Census Tract and another bounding box for unwalkable Census Tract.
# As long as you generate what's needed for the subsequent codes, you are good. The numbered list of tasks below is to provide some hints.
# 1. Write the GEOID of walkable & unwalkable Census Tracts. e.g., tr1_ID <- c("13121001205", "13121001206")
# 2. Extract the selected Census Tracts using tr1_ID & tr2_ID
# 3. Create their bounding boxes using st_bbox(), and
# 4. assign them to tract_1_bb and tract_1_bb, respectively.
# For the walkable Census Tract(s)
# 1.
tr1_ID <- c("13121001205", "13121001206")# **YOUR CODE HERE..** --> For example, tr1_ID <- c("13121001205", "13121001206").
# 2~4
tract_1_bb <- tract %>%
# **YOUR CODE HERE..**
filter(GEOID %in% tr1_ID) %>%
st_bbox()
# For the unwalkable Census Tract(s)
# 1.
tr2_ID <- c("13121000601", "13121000602")# **YOUR CODE HERE..**
# 2~4
tract_2_bb <- tract %>%
# **YOUR CODE HERE..**
# //TASK //////////////////////////////////////////////////////////////////////
filter(GEOID %in% tr2_ID) %>%
st_bbox()
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Get OSM data for the two bounding box
osm_1 <- opq(bbox = tract_1_bb) %>%
add_osm_feature(key = 'highway',
value = c("motorway", "trunk", "primary",
"secondary", "tertiary", "unclassified",
"residential")) %>%
osmdata_sf() %>%
osm_poly2line()
osm_2 <- opq(bbox = tract_2_bb) %>%
add_osm_feature(key = 'highway',
value = c("motorway", "trunk", "primary",
"secondary", "tertiary", "unclassified",
"residential")) %>%
osmdata_sf() %>%
osm_poly2line()
# =========== NO MODIFY ZONE ENDS HERE ========================================
# TASK ////////////////////////////////////////////////////////////////////////
# 1. Convert osm_1 and osm_2 to sfnetworks objects (set directed = FALSE)
# 2. Clean the network by (1) deleting parallel lines and loops, (2) create missing nodes, and (3) remove pseudo nodes,
# 3. Add a new column named length using edge_length() function.
net1 <- osm_1$osm_lines %>%
# Drop redundant columns
select(osm_id, highway) %>%
# **YOUR CODE HERE..**
sfnetworks::as_sfnetwork(directed = FALSE) %>%
activate("edges") %>%
filter(!edge_is_multiple()) %>% # remove duplicated edges
filter(!edge_is_loop()) %>% # remove loops
convert(., to_spatial_subdivision) %>% # subdivide edges
convert(., to_spatial_smooth) %>% # delete pseudo nodes
mutate(length = edge_length())
## Warning: to_spatial_subdivision assumes attributes are constant over geometries
net2 <- osm_2$osm_lines %>%
# Drop redundant columns
select(osm_id, highway) %>%
# **YOUR CODE HERE..**
sfnetworks::as_sfnetwork(directed = FALSE) %>%
activate("edges") %>%
filter(!edge_is_multiple()) %>% # remove duplicated edges
filter(!edge_is_loop()) %>% # remove loops
convert(., to_spatial_subdivision) %>% # subdivide edges
convert(., to_spatial_smooth) %>% # delete pseudo nodes
mutate(length = edge_length())
## Warning: to_spatial_subdivision assumes attributes are constant over geometries
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# OSM for the walkable part
edges_1 <- net1 %>%
# Extract 'edges'
st_as_sf("edges") %>%
# Drop segments that are too short (100m)
mutate(length = as.vector(length)) %>%
filter(length > 100) %>%
# Add a unique ID for each edge
mutate(edge_id = seq(1,nrow(.)),
is_walkable = "walkable")
# OSM for the unwalkable part
edges_2 <- net2 %>%
# Extract 'edges'
st_as_sf("edges") %>%
# Drop segments that are too short (100m)
mutate(length = as.vector(length)) %>%
filter(length > 100) %>%
# Add a unique ID for each edge
mutate(edge_id = seq(1,nrow(.)),
is_walkable = "unwalkable")
# Merge the two
edges <- bind_rows(edges_1, edges_2)
# =========== NO MODIFY ZONE ENDS HERE ========================================
Section 2. OSM, GSV, and computer vision.
Step 2. Define getAzimuth()
function.
getAzimuth <- function(line){
# This function takes one edge (i.e., a street segment) as an input and
# outputs a data frame with four points (start, mid1, mid2, and end) and their azimuth.
# TASK ////////////////////////////////////////////////////////////////////////
# 1. From `line` object, extract the coordinates using st_coordinates() and extract the first two rows.
# 2. Use atan2() function to calculate the azimuth in degree.
# Make sure to adjust the value such that 0 is north, 90 is east, 180 is south, and 270 is west.
# 1
start_p <- line %>%
# **YOUR CODE HERE..**
st_coordinates() %>%
.[1:2,1:2]
# 2
start_azi <- atan2(start_p[2,"X"] - start_p[1, "X"],
start_p[2,"Y"] - start_p[1, "Y"])*180/pi
# //TASK //////////////////////////////////////////////////////////////////////
# TASK ////////////////////////////////////////////////////////////////////////
# Repeat what you did above, but for last two rows (instead of the first two rows).
# Remember to flip the azimuth so that the camera would be looking at the street that's being measured
end_p <- line %>%
# **YOUR CODE HERE..**
st_coordinates() %>%
.[(nrow(.)-1):nrow(.),1:2]
end_azi <- atan2(end_p[2,"X"] - end_p[1, "X"],
end_p[2,"Y"] - end_p[1, "Y"])*180/pi
end_azi <- if (end_azi < 180) {end_azi + 180} else {end_azi - 180}
# //TASK //////////////////////////////////////////////////////////////////////
# TASK ////////////////////////////////////////////////////////////////////////
# 1. From `line` object, use st_line_sample() function to generate points at 0.45 and 0.55 locations. These two points will be used to calculate the azimuth.
# 2. Use st_case() function to convert 'MULTIPOINT' object to 'POINT' object.
# 3. Extract coordinates using st_coordinates().
# 4. Use atan2() functino to Calculate azimuth.
# 5. Use st_line_sample() again to generate a point at 0.5 location and get its coordinates. This point will be the location at which GSV image will be downloaded.
mid_p <- line %>%
# **YOUR CODE HERE..** --> For 0.45 & 0.55 points
st_line_sample(sample = c(0.45, 0.55)) %>%
st_cast("POINT") %>%
st_coordinates()
mid_azi <- atan2(mid_p[2,"X"] - mid_p[1, "X"],
mid_p[2,"Y"] - mid_p[1, "Y"])*180/pi
mid_p <- line %>%
# **YOUR CODE HERE..** --> For 0.5 point
st_line_sample(sample = 0.5) %>%
st_cast("POINT") %>%
st_coordinates() %>%
.[1,]
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
return(tribble(
~type, ~X, ~Y, ~azi,
"start", start_p[1,"X"], start_p[1,"Y"], start_azi,
"mid1", mid_p["X"], mid_p["Y"], mid_azi,
"mid2", mid_p["X"], mid_p["Y"], ifelse(mid_azi < 180, mid_azi + 180, mid_azi - 180),
"end", end_p[2,"X"], end_p[2,"Y"], end_azi))
# =========== NO MODIFY ZONE ENDS HERE ========================================
}
Step 3. Apply the function to all street segments
We can apply getAzimuth()
function to the edges object.
We finally append edges
object to make use of the columns
in edges
object (e.g., is_walkable
column).
When you are finished with this code chunk, you will be ready to
download GSV images.
# TASK ////////////////////////////////////////////////////////////////////////
# Apply getAzimuth() function to all edges.
# Remember that you need to pass edges object to st_geometry() before you apply getAzimuth()
edges_azi <- edges %>%
# **YOUR CODE HERE..**
st_geometry() %>%
map_df(getAzimuth, .progress = T)
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
edges_azi <- edges_azi %>%
bind_cols(edges %>%
st_drop_geometry() %>%
slice(rep(1:nrow(edges),each=4))) %>%
st_as_sf(coords = c("X", "Y"), crs = 4326, remove=FALSE) %>%
mutate(node_id = seq(1, nrow(.)))
# =========== NO MODIFY ZONE ENDS HERE ========================================
Step 4. Define a function that formats request URL and download images.
getImage <- function(iterrow){
# This function takes one row of edges_azi and downloads GSV image using the information from edges_azi.
# TASK ////////////////////////////////////////////////////////////////////////
# Finish this function definition.
# 1. Extract required information from the row of edges_azi, including
# type (i.e., start, mid1, mid2, end), location, heading, edge_id, node_id, and key.
# 2. Format the full URL and store it in `request`. Refer to this page: https://developers.google.com/maps/documentation/streetview/request-streetview
# 3. Format the full path (including the file name) of the image being downloaded and store it in `fpath`
type <- iterrow$type # **YOUR CODE HERE..**
location <- paste0(iterrow$Y %>% round(5), ",", iterrow$X %>% round(5)) # **YOUR CODE HERE..**
heading <- iterrow$azi %>% round(1) # **YOUR CODE HERE..**
edge_id <- iterrow$edge_id # **YOUR CODE HERE..**
node_id <- iterrow$node_id # **YOUR CODE HERE..**
key <- Sys.getenv("GSV_API") # **YOUR CODE HERE..**
endpoint <- "https://maps.googleapis.com/maps/api/streetview" # **YOUR CODE HERE..**
furl <- glue::glue("{endpoint}?size=640x640&location={location}&heading={heading}&fov=90&pitch=0&key={key}") # **YOUR CODE HERE..**
fname <- glue::glue("GSV-nid_{node_id}-eid_{edge_id}-type_{type}-Location_{location}-heading_{heading}.jpg") # Don't change this code for fname
fpath <- here("major-assignment", "major2", "downloaded_image", fname)# **YOUR CODE HERE..**
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Download images
if (!file.exists(fpath)){
download.file(furl, fpath, mode = 'wb')
}
# =========== NO MODIFY ZONE ENDS HERE ========================================
}
Step 5. Download GSV images
Before you download GSV images, make sure
the row number of edges_azi
is not too large! The row
number of edges_azi
will be the number of GSV images you
will be downloading. Before you download images, always double-check
your Google Cloud Console’s Billing tab to make sure that you will not
go above the free credit of $200 each month. The price is $7 per 1000
images.
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Loop!
for (i in seq(1,nrow(edges_azi))){
getImage(edges_azi[i,])
}
# =========== NO MODIFY ZONE ENDS HERE ========================================
ZIP THE DOWNLOADED IMAGES AND NAME IT ‘gsv_images.zip’ FOR STEP 6.
Step 6. Apply computer vision
Now, use Google Colab to apply the semantic segmentation model. Zip your images and upload the images to your Colab session.
Step 7. Merging the processed data back to R
Once all of the images are processed and saved in your Colab session as a CSV file, download the CSV file and merge it back to edges.
# TASK ////////////////////////////////////////////////////////////////////////
# Read the downloaded CSV file from Google Colab
seg_output <- read.csv(
# **YOUR CODE HERE..**
"seg_output.csv"
) %>%
mutate(node_id = img_id)
# //TASK ////////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Join the seg_output object back to edges_azi object using node_id as the join key.
edges_seg_output <- edges_azi %>%
inner_join(seg_output, by="node_id") %>%
select(type, X, Y, node_id, building, sky, tree, road, sidewalk) %>%
mutate(across(c(building, sky, tree, road, sidewalk), function(x) x/(640*640)))
# =========== NO MODIFY ZONE ENDS HERE ========================================
Section 3. Summarise and analyze the results.
At the beginning of this assignment, you defined one Census Tract as walkable and the other as unwalkable. The key to the following analysis is the comparison between walkable and unwalkable Census Tracts.
Analysis 1 - Create interactive map(s) to visualize the spatial distribution of the streetscape.
You need to create maps of the proportion of building, sky, tree, road, and sidewalk for walkable and unwalkable areas. In total, you will have 10 maps.
Provide a brief description of your findings from the maps.
# TASK ////////////////////////////////////////////////////////////////////////
# Create interactive map(s) to visualize the `edges_seg_output` objects.
# As long as you can deliver the message clearly, you can use any format/package you want.
walkable_node <- edges_azi %>%
filter(is_walkable == "walkable") %>%
pull(., node_id)
unwalkable_node <- edges_azi %>%
filter(is_walkable == "unwalkable") %>%
pull(., node_id)
edges_seg_output_walkable = edges_seg_output %>%
filter(node_id %in% walkable_node)
edges_seg_output_unwalkable = edges_seg_output %>%
filter(node_id %in% unwalkable_node)
t1_walkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_walkable) +
tm_dots(col = "building", style="quantile", palette = 'viridis') +
tm_layout(title = "Walkable - Building %")
t2_walkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_walkable) +
tm_dots(col = "sky", style="quantile", palette = 'viridis') +
tm_layout(title = "Walkable - Sky %")
t3_walkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_walkable) +
tm_dots(col = "tree", style="quantile", palette = 'viridis') +
tm_layout(title = "Walkable - Tree %")
t4_walkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_walkable) +
tm_dots(col = "road", style="quantile", palette = 'viridis') +
tm_layout(title = "Walkable - Road %")
t5_walkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_walkable) +
tm_dots(col = "sidewalk", style="quantile", palette = 'viridis') +
tm_layout(title = "Walkable - Sidewalk %")
t1_unwalkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_unwalkable) +
tm_dots(col = "building", style="quantile", palette = 'viridis') +
tm_layout(title = "Unwalkable - Building %")
t2_unwalkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_unwalkable) +
tm_dots(col = "sky", style="quantile", palette = 'viridis') +
tm_layout(title = "Unwalkable - Sky %")
t3_unwalkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_unwalkable) +
tm_dots(col = "tree", style="quantile", palette = 'viridis') +
tm_layout(title = "Unwalkable - Tree %")
t4_unwalkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_unwalkable) +
tm_dots(col = "road", style="quantile", palette = 'viridis') +
tm_layout(title = "Unwalkable - Road %")
t5_unwalkable <- tm_basemap("OpenStreetMap")+
tm_shape(edges_seg_output_unwalkable) +
tm_dots(col = "sidewalk", style="quantile", palette = 'viridis') +
tm_layout(title = "Unwalkable - Sidewalk %")
tmap_mode("view")
## tmap mode set to interactive viewing
# Building
tmap_arrange(t1_walkable,
t1_unwalkable,
ncol = 2)
# Sky
tmap_arrange(t2_walkable,
t2_unwalkable,
ncol = 2)
# Tree
tmap_arrange(t3_walkable,
t3_unwalkable,
ncol = 2)
# Road
tmap_arrange(t4_walkable,
t4_unwalkable,
ncol = 2)
# Sidewalk
tmap_arrange(t5_walkable,
t5_unwalkable,
ncol = 2)
# //TASK //////////////////////////////////////////////////////////////////////
Brief Description: The maps show that walkable areas tend to have more sidewalks, trees, and buildings, which are features that make walking easier and more pleasant. On the other hand, unwalkable areas have more roads and fewer sidewalks, indicating they are designed more for cars than for pedestrians. When comparing the distribution patterns of pixel proportions between walkable and unwalkable areas, the walkable areas display a sparse distribution of all five categories, with no significant pattern. In contrast, the unwalkable areas show clusters where there is a high density of buildings, trees, roads, and sidewalks.
Analysis 2 - Compare the means.
You need to calculate the mean of the proportion of building, sky, tree, road, and sidewalk for walkable and unwalkable areas. For example, you need to calculate the mean of building category for each of walkable and unwalkable Census Tracts. Then, you need to calculate the mean of sky category for each of walkable and unwalkable Census Tracts. In total, you will have 10 mean values. Provide a brief description of your findings.
# TASK ////////////////////////////////////////////////////////////////////////
# Perform the calculation as described above.
# As long as you can deliver the message clearly, you can use any format/package you want.
walkable_mean <- edges_seg_output_walkable %>%
group_by() %>%
summarize(building_mean = mean(building),
sky_mean = mean(sky),
tree_mean = mean(tree),
road_mean = mean(road),
sidewalk_mean = mean(sidewalk)) %>%
st_drop_geometry() %>%
mutate(walkable = "walkable")
unwalkable_mean <- edges_seg_output_unwalkable %>%
group_by() %>%
summarize(building_mean = mean(building),
sky_mean = mean(sky),
tree_mean = mean(tree),
road_mean = mean(road),
sidewalk_mean = mean(sidewalk)) %>%
st_drop_geometry() %>%
mutate(walkable = "unwalkable")
print(walkable_mean)
## # A tibble: 1 × 6
## building_mean sky_mean tree_mean road_mean sidewalk_mean walkable
## <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 0.235 0.139 0.164 0.346 0.0653 walkable
print(unwalkable_mean)
## # A tibble: 1 × 6
## building_mean sky_mean tree_mean road_mean sidewalk_mean walkable
## <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 0.109 0.268 0.145 0.352 0.0351 unwalkable
df_mean <- bind_rows(walkable_mean, unwalkable_mean) %>%
pivot_longer(.,
c('building_mean', 'sky_mean', 'tree_mean', "road_mean", 'sidewalk_mean'),
names_to = 'objects',
values_to = 'mean')
ggplot(df_mean, aes(fill=walkable, y=mean, x=objects)) +
geom_bar(position="dodge", stat="identity")
# //TASK //////////////////////////////////////////////////////////////////////
Brief Description: I use the bar chart provides a comparative analysis of the average distributions between walkable and unwalkable areas. It shows that walkable areas have higher mean values for buildings, sidewalks, and trees, suggesting these elements are more significant in walkable environments. In contrast, unwalkable areas have a slightly higher mean for roads, indicating a greater presence of road infrastructure. Notably, the mean for the sky is significantly higher in unwalkable areas, which may indicate open spaces with less overhead cover, possibly due to a lack of buildings or tree coverage. This pattern suggests that walkable areas tend to be more enclosed by buildings and vegetation, while unwalkable areas are more open, with limited pedestrian infrastructure.
Analysis 3 - Draw boxplot
You need to calculate the mean of the proportion of building, sky, tree, road, and sidewalk for walkable and unwalkable areas. For example, you need to calculate the mean of building category for each of walkable and unwalkable Census Tracts. Then, you need to calculate the mean of sky category for each of walkable and unwalkable Census Tracts. In total, you will have 10 mean values. Provide a brief description of your findings.
# TASK ////////////////////////////////////////////////////////////////////////
# Create boxplot(s) using ggplot2 package.
edges_seg_output_melt <- edges_seg_output %>%
mutate(walkable = case_when(node_id %in% walkable_node ~ "walkable",
node_id %in% unwalkable_node ~ "unwalkable")) %>%
pivot_longer(cols = c("building", "sky", "tree", "road", "sidewalk"),
names_to = c("measure_type"),
values_to = c("value"))
ggplot(data=edges_seg_output_melt) +
geom_boxplot(aes(x = walkable, y=value, group=walkable),
color = "black") +
labs(
y = "Proportion of Objects in the Images",
title = "Box Plot - Proportion of Object in the Images of Walkable and Unwalkable Areas"
) +
facet_wrap(~measure_type, ncol=5) +
scale_fill_brewer(palette = "Blues") +
theme_minimal() +
theme(strip.text = element_text(face = "bold"))
# //TASK //////////////////////////////////////////////////////////////////////
Brief Description: The box plots show that walkable areas have a noticeably higher proportion of buildings and sidewalks compared to unwalkable areas, meaning that these features are more common in places designed for walking. On the other hand, unwalkable areas have a much higher proportion of sky, indicating more open space or less overhead coverage, like buildings or trees. The proportions of roads and trees are fairly similar between walkable and unwalkable areas, showing no major differences in these features.
Conclusion
In summary, the analysis shows that walkable areas, such as Tech Square, are more pedestrian-friendly, with higher proportions of buildings, sidewalks, and trees that provide a sense of enclosure and support for walking. In contrast, unwalkable areas, like the northwest side of the Georgia Tech campus, have more open spaces and a significantly higher proportion of sky, indicating limited overhead coverage and pedestrian infrastructure. The maps and visual data reveal that walkable areas have a dispersed distribution of features, while unwalkable areas display clusters of buildings, trees, roads, and sidewalks, resulting in a less consistent walking environment. This analysis effectively demonstrates the capability of using computer vision on street view images to assess the walkability of an area, which can be applied to evaluate walkability in other locations in a scalable, unsupervised manner.