Grid Search: Finding the Most Convenient Block to Live on in New York City

The house I grew up in was so close to my high school that if I poked my head out of our bathroom window on the top floor, I could actually see my fellow students scampering around the yard during their PE classes. On occasion I’d daydream about how much time I might save if I could somehow string a zipline between the two endpoints and glide onto campus every morning.

Not that my three-minute commute was at all unmanageable. Because our house and the school occupied the same Brooklyn block, I could get from breakfast to Biology simply by tracing the perimeter of our shared rectangle, no crosswalks required. This level of self-containment comforted me — I liked knowing that if all of the surrounding streets spontaneously sunk to the center of the earth, separating each block into its own island, well then I could still get to school everyday, and even pick up a croissant from Ozzie’s along the way.

My little island growing up

I’ve carried this appreciation for proximity into my adulthood, during which I’ve lived in a handful of apartments across various Manhattan neighborhoods. There’s something extra satisfying about being able to procure a coffee, a beer, a roll of toilet paper without straying from your block of origin. And so I wondered: which block in New York City has the most resources? If you wanted to get the most out of life without ever crossing the street, where should you live? In a city more or less devoid of traditional malls, can we recreate one in the aggregate on a single block?

To answer this question we need to first divide the city up into the blocks created by its signature grid pattern. To do so, we take a shapefile of New York City streets (sourced from NY.gov) and remove that area from a larger shapefile of the five boroughs (sourced from NYC OpenData). It’s kind of like the streets are a giant cookie cutter that we’re using to press out individual biscuits from the city’s terrain.

Next, we need to list and locate as many of the city’s businesses – restaurants, bars, supermarkets, gyms, laundromats, etc. – as we can. Thankfully, the folks at Open Street Maps provide their Overpass API, which, given a geographical bounding box, will tell you exactly what they can find within it, down to park benches and bike racks. (It should be noted that there appears to be some missing locations from this dataset, especially as you stray further from Manhattan; while I was able to verify the map’s accuracy in regions close to my Upper East Side apartment, I couldn’t find some of my go-to lunch spots near my office in the Bronx.)

Here is where the the analysis tips over into unavoidable subjectivity. How do we define a utility function to grade the resources available at each city block? As far as I’m concerned, a Chipotle, a Dunkin’, two bars, a laundromat, and a gym would more than suffice (if you can’t tell, I’m a man of culture). But my mom would probably like a yoga studio, a sushi restaurant, and a flower store. And beyond preference, there’s the question of saturation – at what point is the marginal value of another restaurant eclipsed by something less exciting, like a post office?

Understanding that I can’t please everybody (or maybe even anybody), I’ve assigned the following points for each category as displayed in the table below. Blocks accumulate points per location for each instance of a particular category, but are also capped at max points in that category – so that, for example, seven restaurants would be no more valuable than six. The most important rows are the high frequency ones, like cafes, bars, and supermarkets, but for the sake of thoroughness I evaluated the utility of every last category available. Yes, I think a “swingerclub” is worth a point (and probably more, to be quite honest).

category frequency points per location max points
restaurant 6673 1 6
fast food 3115 1 6
cafe 2021 1 4
convenience 1656 3 9
place of worship 1614 1 2
bar 1436 1 4
clothes 1431 2 4
school 1392 0 0
hairdresser 1152 2 4
beauty 1083 2 4
pharmacy 991 3 6
bank 990 2 4
bicycle rental 843 0 0
supermarket 798 4 12
alcohol 689 2 4
deli 666 2 6
laundry 604 2 4
bakery 591 1 2
mobile phone 491 0 0
fitness centre 478 4 8
dry cleaning 419 2 4
dentist 411 0 0
clinic 376 3 6
doctors 363 0 0
variety store 349 2 4
park 331 4 8
jewelry 291 1 2
car repair 288 0 0
post box 287 1 1
optician 281 0 0
fuel 265 0 0
ice cream 243 2 4
post office 236 1 1
fire station 235 3 3
shoes 235 2 4
gift 234 2 4
furniture 203 1 1
tobacco 196 1 2
florist 175 1 1
hardware 165 2 2
kindergarten 157 0 0
bicycle 156 0 0
department store 153 2 4
massage 151 3 3
theatre 150 3 6
cosmetics 144 2 4
greengrocer 140 2 4
childcare 136 4 4
books 134 2 4
social facility 133 0 0
atm 118 1 2
sports centre 117 3 6
pet 116 0 0
butcher 110 2 4
car 108 0 0
veterinary 104 3 3
library 102 3 3
tattoo 97 0 0
arts centre 95 2 4
cannabis 89 2 4
nightclub 89 2 4
electronics 88 1 2
police 88 3 3
car rental 83 0 0
community centre 75 3 3
events venue 72 3 3
sports 71 3 3
telephone 71 1 1
nutrition supplements 70 2 4
public bookcase 70 1 1
copyshop 68 2 2
yes 68 0 0
car parts 66 0 0
confectionery 64 2 4
funeral directors 64 0 0
marketplace 64 3 3
stationery 62 2 2
garden 61 3 3
seafood 60 1 3
studio 60 2 2
houseware 58 2 2
cinema 56 3 6
dance 56 3 6
parking 56 0 0
toys 55 2 2
trade 55 2 2
tyres 55 0 0
charging station 52 0 0
craft 52 2 2
art 47 2 2
ferry terminal 47 0 0
kitchen 47 2 2
newsagent 46 1 1
bed 44 0 0
e-cigarette 44 1 1
paint 42 1 1
pet grooming 41 2 2
playground 41 3 3
second hand 41 2 4
wine 40 2 4
party 39 2 2
taxi 39 0 0
car wash 38 0 0
chocolate 38 1 2
driving school 38 0 0
video games 38 2 2
beverages 37 1 3
hospital 37 4 4
parking entrance 36 0 0
storage rental 36 0 0
tailor 36 2 4
travel agency 35 0 0
wholesale 35 2 2
doityourself 34 1 1
pawnbroker 34 2 2
perfumery 34 2 2
charity 33 1 1
frame 33 1 1
interior decoration 33 2 2
music 33 2 4
antiques 32 2 4
fashion accessories 32 1 2
appliance 31 2 4
college 31 0 0
locksmith 31 1 1
animal boarding 30 2 4
university 30 0 0
fabric 29 1 1
music school 28 0 0
drinking water 27 0 0
bag 26 0 0
food court 26 3 3
coffee 25 1 3
herbalist 25 1 1
religion 25 1 1
computer 24 2 2
medical supply 23 2 2
musical instrument 23 2 2
carpet 21 0 0
courthouse 21 0 0
erotic 21 2 2
photo 21 2 2
bus station 20 0 0
pitch 20 0 0
garden centre 19 2 4
tea 19 1 3
grave yard 18 0 0
lighting 18 1 1
baby goods 17 1 1
health food 17 1 2
watches 17 1 1
tiles 16 1 1
bureau de change 15 1 1
general 15 1 1
social centre 15 1 1
townhall 15 1 1
hairdresser supply 14 1 1
bbq 13 1 2
marina 13 0 0
stripclub 13 2 4
vending machine 13 1 1
cheese 12 2 4
fountain 12 0 0
motorcycle 12 0 0
outdoor 12 0 0
sewing 12 1 1
toilets 12 0 0
window blind 12 1 1
money lender 11 1 1
telecommunication 11 1 1
ticket 11 0 0
bathroom furnishing 10 1 1
bicycle parking 9 0 0
boat rental 9 0 0
clock 9 0 0
farm 9 0 0
flooring 9 1 1
language school 9 0 0
vacant 9 0 0
animal shelter 8 1 1
biergarten 8 1 3
collector 8 1 1
escape game 8 0 0
hackerspace 8 0 0
shelter 8 0 0
doors 7 0 0
leather 7 0 0
mall 7 4 4
miniature golf 7 1 1
vacuum cleaner 7 0 0
games 6 1 1
parcel locker 6 0 0
slipway 6 0 0
video 6 1 1
car sharing 5 0 0
dog park 5 3 3
hearing aids 5 0 0
ice rink 5 0 0
internet cafe 5 1 2
swimming pool 5 3 3
bench 4 0 0
camera 4 1 1
curtain 4 1 1
electrical 4 1 1
give box 4 0 0
military surplus 4 1 1
money transfer 4 1 1
music venue 4 3 3
nature reserve 4 3 3
nursing home 4 0 0
public bath 4 0 0
recycling 4 0 0
training 4 0 0
amusement arcade 3 2 4
fitness station 3 2 4
hifi 3 0 0
pasta 3 1 1
public building 3 0 0
security 3 0 0
trophy 3 0 0
anime 2 1 1
boat 2 0 0
boutique 2 2 4
common 2 0 0
fishing 2 0 0
fortune teller 2 1 1
frozen food 2 1 2
gambling 2 1 1
glaziery 2 0 0
monastery 2 0 0
motorcycle parking 2 0 0
prison 2 0 0
research institute 2 1 1
spices 2 1 1
waste basket 2 0 0
weapons 2 1 1
beach resort 1 0 0
bookmaker 1 0 0
brewing supplies 1 0 0
cafe;bar 1 1 1
casino 1 2 2
conference centre 1 1 1
dojo 1 0 0
driver training 1 0 0
graphic design 1 1 1
household linen 1 1 1
lottery 1 1 1
outpost 1 0 0
pest control 1 0 0
planetarium 1 0 0
post depot 1 0 0
ranger station 1 0 0
spa 1 3 3
swingerclub 1 1 1
vehicle inspection 1 0 0
waste transfer station 1 0 0
wool 1 0 0

So now that we have a map and a scoring system, which blocks provide the most convenience to their inhabitants? If you limit your search to areas smaller than 200,000 square feet, so as to avoid useless answers like LaGuardia Airport, two Manhattan blocks surpass fifty points and take our top spots. In second place is the block between 32nd and 33rd Streets and 5th and Broadway avenues in the Koreatown neighborhood of Manhattan. As demonstrated below, this block has over a dozen food options – many of them Korean barbecue – and also cafes, convenience stores, four bars, both a Walgreens and a CVS, a bank, a bookstore, and a massage parlor. So packed with stores and restaurants is the segment along 32nd street that it’s often referred to as the heart of Koreatown and has even been nicknamed “Korea Way”.

Koreatown is more of a commercial area than a residential one though, so it’s harder to imagine actually living on top of this stretch of businesses. More intriguing is the first place finisher located in the East Village, a neighborhood overflowing with so much culture that young New Yorkers are willing to squeeze themselves into small, pricey, oddly-shaped apartments just to experience it. This block is surrounded by 9th St, 10th St, 1st Avenue, and 2nd Avenue and it too has plenty of dining options, but also a grocery store, a nail salon, a laundromat, a dentist, an optician, and even a butcher.

The many businesses of our top ranked block

Are this block’s inhabitants aware that they reside atop arguably the peak of New York City convenience? To answer that question, I had to engage in some field work, a rare task for the sedentary blogger. So on a cold Monday night in January, I posted up on 10th St and 2nd Avenue and asked some of the local residents if they had considered their block’s abundant resources and taken advantage of them accordingly.

One man noted how easy it was to “go for a bite or a drink,” while another praised the convenience store that was just around the corner. Two people listed Madame Vo, a Vietnamese restaurant on 10th St, as a frequent destination. But overall they didn’t share the appreciation I had for their block’s top status, and were quick to list businesses that fell outside of their atomic rectangle, violating the constraints of my optimization. It appears that an obsession with extreme proximity might be reserved for people lucky enough to have lived around the corner from school.

In any case, below are the top ten blocks in our dataset with additional details available if you expand the locations header:

borough streets points
Manhattan E 10th St, E 9th St, 1st Ave, 2nd Ave 54
locations
restaurant (5): Madame Vo, Shabu-Tatsu, Rai Rai Ken, Graffitti, Uluh
fast food (1): Rowdy Rooster
cafe (3): MUD, Hard to Explain, La Cabra
convenience (3): Sweet Village Market Place, Beer & Smoke Shop, Village Farm Grocery
bar (2): The Immigrant, Stickett Inn
clothes (3): Duo Nyc, Spark Pretty, Ibiza
hairdresser (3): Uliana, Art+Ray, Kaminotech
beauty (1): Yosei Nail
bank (1): Chase
laundry (1): New Sew Good Cleaners
dentist (1): Terencia S. Q. Conejero, D.D.S, MPH
jewelry (1): SHW
optician (2): Eye & Health, Fabulous Fanny’s
ice cream (1): Davey’s Ice Cream
post office (1): United Shipping and Packaging
gift (1): Spooksvilla & Friends
tobacco (1): Vape N Smoke
theatre (1): Theater for the New City
atm (1): Chase
butcher (1): Honest Chops
cannabis (1): Pride Smokes
copyshop (1): The Source Unltd
houseware (1): Rosemary Home
fashion accessories (1): Aliens of Brooklyn
Manhattan Broadway, W 32nd St, W 33rd St, 5th Ave 52
locations
restaurant (14): BCD Tofu House, Turntable Chicken Jazz, Gammeeok, UDON, New Wonjo, Five Senses, BB.Q Olive Chicken, Anytime NYC, Antoya Korean BBQ, The Press Club Grill, Nan Xiang Xiao Long Bao, Rib No. 7, Hutaoli, Seoul Salon
fast food (4): Naya, Market Crates, Dunkin’, Panera Bread
cafe (5): caffé bene, Grace Street, Tea Makers, Machi machi, Early Edition Espresso Bar
convenience (1): Herald Smoke Shop
bar (4): Legends, Jack Demsey’s, Pocha 32, Sir John’s NY
clothes (1): My.Suit
hairdresser (1): Kakaboka
beauty (1): Juvenex Spa
pharmacy (2): Walgreens, CVS Pharmacy
bank (1): Chase
bakery (1): Tous les Jours
ice cream (1): Pinkberry
gift (1): Platform 32
massage (1): Solle Spa
cosmetics (2): Kosette Beauty Market, L’ovue Beauty & Mask
books (1): Koryo books
food court (1): Food Gallery 32
Queens Horace Harding Expy, Springfield Blvd, NA 49
locations
restaurant (6): Chilsung Garden, Little Dumpling Bayside, New York Ban Jum, Empire Garden, Rolly Kimbab, Imperial Taste
fast food (5): Slims Bagels & Deli, Kokio Chicken & Beer, Thank you Donuts & Corn Dogs, Subway, The Empanada Spot
cafe (2): Starbucks, Anda Boba Tea
convenience (1): Red Arrow Store
hairdresser (2): Hair Sketch, Springfield Barber & Hair Styling
beauty (2): M & M Beauty Box, Springfield Nail Spa
pharmacy (2): Bayside Pharmacy, Walgreens
bank (1): Chase
supermarket (1): New Mart
laundry (1): Bayside Laundromat
dry cleaning (1): Skyview Cleaners
dentist (1): United Dental Group
ice cream (1): Red Mango
post office (2): The UPS Store, USPS Oakland Garden Branch
atm (1): Bank of America
car (1): Motive Auto
sports (1): Spring Golf
wine (1): Springfield Wine & Spirits
Queens 70th Rd, Austin St, Queens Blvd, 71st Ave 48
locations
restaurant (8): MoCA Asian Bistro, (aged.), Cabana Restaurant & Bar, Bangkok Cuisine, Numero 28, JK Wings & Fish, Nan Xiang Soup Dumplings, Oba
fast food (1): Dunkin’
convenience (1): 7-Eleven
bar (1): The Billiard Company
clothes (1): Fox’s Designer Off Price
hairdresser (1): SIW
bank (3): HSBC, Citibank, Flushing Bank
deli (1): Mom & Sons Deli
bakery (2): Fayda, Gotta Getta Bagel
mobile phone (1): T-Mobile
fitness centre (2): Orangetheory Fitness, Club Pilates
dentist (1): Horizon Dental
clinic (2): CityMD, Level Up MD
variety store (1): Jenbro
optician (1): American Vision Center
ice cream (1): Red Mango
bicycle (1): Trek
massage (1): M Beauty Spa
cannabis (1): Curaleaf
nutrition supplements (1): The Vitamin Shoppe
Manhattan Cooper Sq, E 7th St, Saint Marks Pl, Taras Shevchenko Pl, 2nd Ave 47
locations
restaurant (8): Paul’s ‘Da Burger Joint’, Ise, Streecha Ukrainian Kitchen, B&H Dairy, 886, Misoya, Four Four South Village, Mamoun’s Falafel
fast food (4): Ray’s Pizza Bagel Cafe, Taqueria Diana, 2 Bros Pizza, Birria L.E.S.
cafe (2): Möge Tee, Poetica Coffee
convenience (1): Village Happy House Convenience
bar (4): Barcade, Standings, Burp Castle, McSorley’s Old Ale House
hairdresser (2): Ace of Bladez, The Blackstones Collective
alcohol (1): St. Marks Wine & Liquor
bakery (1): Bake Culture
fitness centre (1): Yoga to the People
jewelry (2): Elite Jewelry & Piercing, Jewels
gift (1): Funky Town East Village
tobacco (2): Addiction NYC, Maze Convenience
theatre (1): St. Mark’s Comedy Club
tattoo (1): St. Marks Piercing & Tattoo
arts centre (1): Arts on Site
copyshop (1): NY Copy Print & Ship Center
e-cigarette (1): i-vape
food court (1): Vegan Food Court
Brooklyn Fulton St, Jay St, Lawrence St, Willoughby St 46
locations
restaurant (2): Yaso Tangbao, Lee’s Villa
fast food (11): Greek Xpress, Mr. Fulton, Supreme Pizza, Pio Bagel, Papa John’s, Dunkin’, Xi’an Famous Foods, Hibachi King Express, Koufu, Hawa Smoothies & Bubble Tea, Yummy Taco
cafe (2): Möge Tee, Gong Cha
convenience (1): Sunrise Mini Mart
clothes (2): Portabella, JD Sports
hairdresser (4): Rakaha Spa Barber Shop, Bofanta African Hair Braiding, Fatima Professional African Hair Braiding, True Salon
beauty (3): JA Nail, Long Nail, Bombay Beauty Salon
pharmacy (1): Faith Pharmacy
alcohol (1): G & I Wine & Spirits
clinic (1): CityMD
jewelry (2): Giovanni Jewelers, Nucastle Jewelers
optician (2): Elite Eye Care, DNA Optical
tobacco (1): Jay St. Convenience
social facility (1): SCO Family of Services
community centre (1): The Solidarity Room Project
art (1): Faith Art Gallery
video games (1): GameStop
beverages (1): Beer Barrel
fashion accessories (1): Wallalde
health food (1): Downtown Natural Market
Queens 40th Rd, Main St, Prince St, Roosevelt Ave 45
locations
restaurant (12): Fu Run, White Bear, Shanghai You Garden Restaurant, King Crab House, 8 Pots Mini Hot Pot, Tian Jin, New Fuzhou Style, Yin Traditional Hot Pot, Malay Restaurant Inc, Legend Chicken, Curry Leaves Restaurant, Oidu Restaurant
fast food (2): Popeyes, McDonald’s
cafe (3): Yi Fang Tea, Tiger Sugar, Maxin Bakery
convenience (1): New York Food & Herbs
clothes (1): Custom Fitted Bras
hairdresser (3): I Beauty House, Sai Kay 21 Beauty Skincare, A & A Beauty Salon
beauty (3): Beauty Cosmetics, Yi Pian Yun Beauty Salon, Beauty Skincare Center
pharmacy (2): All Rx Pharmacy, Rainbow Care Pharmacy
bank (2): Cathay Bank, Cathay Bank
bakery (2): Eggcellent Soufflé Pancake, Eggcellent Pancakes
mobile phone (1): T-Mobile
jewelry (1): Ngan Ping Jewelry
ice cream (1): Flushing Ice Cream Factory
shoes (1): Jai Mei Shoe Store
tattoo (1): Tattoo Studio
marketplace (2): East Mall, Shopping Mall
travel agency (5): Sincere Travel, Fei Yang Travel, Selectravel & Tour, LLC, VIP Travel Services, Asian American Travel
tea (1): With Sugar & Tea
Manhattan Bowery, Chrystie St, Grand St, Hester St 44
locations
restaurant (7): Ming Kee Kitchen, Ph 87, Nam Son, Lao Jie Hot Pot & BBQ, New Kim Tuong, Jiang Nan, Kitchen Cô Út
fast food (4): Go Believe Bakery, Wah Fung Fast Food, Good Century Cafe, Chi Dumpling House
cafe (2): Kung Fu Tea, New Kamboat Bakery & Cafe
convenience (1): Grand Advance Trading
bar (1): Grand Master 95
beauty (2): Vanity Projects, Aroma Beauty
pharmacy (1): Bowery Pharmacy
bank (1): Hanover Bank
supermarket (2): Tan Tin Hung Supermarket, Super A Mart
mobile phone (1): 101
furniture (2): Cozy Living Furniture, International Furniture Company
hardware (1): T & Y Hardware
sports centre (1): Tak Wah Kung Fu Club
seafood (2): Bor Kee Food Market, CT Seafood Marts
trade (1): L & J Restaurant Mfg.
herbalist (1): F & R Trading
Manhattan E 84th St, E 85th St, 2nd Ave, 3rd Ave 44
locations
restaurant (7): Gracie’s Diner, Budapest Café & Restaurant, Jacques Brasserie, Green Kitchen, Elio’s, Vanessa’s Dumpling House, Giacomo’s Pizza
fast food (2): McDonald’s, Chipotle
cafe (1): Caroline’s Donuts
bar (3): Ethyl’s Alcohol & Food, Trinity Pub, Brandy’s Piano Bar
hairdresser (1): Davids Barber Shop
beauty (1): Glosslab
laundry (1): Cleaners & Laundry
fitness centre (1): Rumble
dry cleaning (1): Wendy Laundry & Dry Cleaning
clinic (1): Tisch Center for Women’s Health
optician (1): Cohen’s Fashion Optical
ice cream (2): Van Leeuwen, Spoonable Spirits
hardware (1): Hercules Plumbing Electrical & Hardware Supply
massage (1): Amenity Spa 85
cosmetics (1): Discount Depot
veterinary (1): Yorkville Animal Hospital
cannabis (1): Smoke Shop
paint (1): Janovic Paint & Decorating Center
Queens 38th St, Broadway, Steinway St, 34th Ave 42
locations
restaurant (2): Slice, Luna Asian Bistro
cafe (3): Love Café, Caffé bene, Kinship Coffee
bar (3): Raven’s Head Public House, Brik, Cronin And Phelan
beauty (1): Lyn’s Nail & Threading
pharmacy (2): Athena’s Pharmacy, Athena’s Pharmacy
deli (2): Steinway Gourmet Deli, KLM Deli
mobile phone (2): T-Mobile, Metro by T-Mobile
fitness centre (2): Shotojuku, Lucille Roberts
dentist (1): Steinway Family Dental
clinic (2): Laserklinic, Pediatric Clinic
variety store (1): 99 Cents And Up NYC
furniture (4): US Furniture, American Design Furniture Corp., Dhaka Furniture, Astoria Furniture
dance (1): Wild Heart Performing Arts Studio
second hand (1): Steinway Thrift Shop
driving school (1): Ferrari Driving School


If you’d like to explore other areas or type in your own NYC zip code, take a look at the accompanying map (be patient with her – she is large and slow):

All code used to analyze the data and display the results can be found below. Thanks for reading!

Code
library(tidyverse)
library(patchwork)
library(sf)
library(osmdata)
library(leaflet)
library(leaflet.extras)
library(htmltools)
library(knitr)
library(kableExtra)
library(stringi)

options(dplyr.summarise.inform = FALSE)
expand.grid.df <- function(...) Reduce(function(...) merge(..., by=NULL), list(...))

coord_sys <- 4326

# shape files
boros <- read_sf("Borough Boundaries")
streets <- read_sf("SimplifiedStreets.shp")
zips <- read_sf("ZIP_CODE_040114") 
zips <- zips %>% st_transform(coord_sys) %>% select(geometry, zip = ZIPCODE) 

# location grading system
points_sys <- read_csv("points_sys.csv")
points_sys <- points_sys %>% mutate(category = str_replace_all(category, "_", " "))

boro_names <- c("New York", "Bronx", "Queens", "Kings")

# take out SI (too suburban), and only take largest land mass of remaining four
boros <- boros %>%
  filter(boro_name != "Staten Island") %>%
  st_cast("POLYGON") %>%
  mutate(area = st_area(.)) %>%
  arrange(desc(area)) %>%
  group_by(boro_name) %>%
  slice(1) %>%
  select(geometry, borough = boro_name)

# we only want NYC streets
streets_nyc <- streets %>% 
  filter(LeftCounty %in% boro_names | RightCount %in% boro_names) %>%
  select(Label, geometry) %>%
  arrange(str_detect(Label, "Ave"))

# share a coordinate system so we can compare/intersect them
boros <- st_transform(boros, st_crs(streets))

# grids are what remains after we remove the (buffed) streets
grids_nyc <- st_difference(
  boros, 
  streets_nyc %>% st_buffer(10) %>% st_union()
) %>%
  st_cast("POLYGON") %>%
  mutate(area = round(st_area(.))) %>%
  # area restrictions should limit us to true blocks
  mutate(grid_idx = row_number()) %>%
  # add the street intersections for identification purposes
  {.$streets <- (.) %>%
    st_intersects(streets_nyc %>% 
                    st_buffer(10 + 1)) %>%
    # paste together the indexed labels
    map(~streets_nyc$Label[.x])
  .
  } %>%
  st_transform(crs = st_crs(coord_sys))



# bounding box for NYC
nyc_bb <- c(-74.2588430, 40.4765780, -73.7002330, 40.9176300)

# places recognized by OSM
all_points <- opq(bbox = nyc_bb, nodes_only = TRUE, timeout = 60*60)

# get every location that falls into any of amenity, shop, leisure
amenity_points <- all_points %>%
  add_osm_feature("amenity", available_tags("amenity")) %>%
  osmdata_sf()

shop_points <- all_points %>%
  add_osm_feature("shop", available_tags("shop")) %>%
  osmdata_sf()

leisure_points <- all_points %>%
  add_osm_feature("leisure", available_tags("leisure")) %>%
  osmdata_sf()

combo_points <- bind_rows(
  leisure_points$osm_points,
  shop_points$osm_points,
  amenity_points$osm_points
) %>%
  select(name, amenity, leisure, shop, geometry) %>%
  filter(!is.na(name)) %>%
  distinct() %>%
  transmute(name, category = coalesce(amenity, leisure, shop)) %>%
  # combine some very similar categories
  mutate(category = case_when(
    category == "pub" ~ "bar",
    category == "chemist" ~ "pharmacy",
    category == "pastry" ~ "bakery",
    TRUE ~ category
  ))

# intersections between grid squares and points
grid_point_ix <- grids_nyc %>% 
  # get coordinates of overlap
  st_intersects(combo_points) %>%
  # cross join each grid to its overlapping points
  imap_dfr(~{
    
    grids_nyc[.y, ] %>% 
      rename(geometry_grid = geometry) %>%
      as.data.frame() %>%
      expand.grid.df(combo_points %>%
                       slice(.x) %>% 
                       rename(geometry_pt = geometry) %>%
                       as.data.frame()
      )
  }
  )

save(grids_nyc, grid_point_ix, file = "grid_items.Rdata")
load("grid_items.Rdata")

# OSM string encoding came in wonky (Latin1?)
fix_string <- function(strings) {
  map_chr(strings, ~
            # no clue why this works
            rawToChar(charToRaw(stri_encode(charToRaw(.x), from = "UTF-8", to = "latin1")))
          )
}


# giant pipe chain that formats blocks for mapping
g <- grid_point_ix %>%
  group_by(grid_idx, category) %>%
  mutate(name = fix_string(name)) %>%
  # total points and list by grid and category
  summarize(n = n(), locs = list(name)) %>%
  mutate(category = str_replace_all(category, "_", " ")) %>%
  inner_join(points_sys %>% select(-n), by = "category") %>%
  mutate(points = pmin(max, n*per)) %>%
  group_by(grid_idx) %>%
  # total points and distinct categories by grid
  mutate(total_points = sum(points), distinct_cat = n_distinct(category)) %>%
  mutate(category = factor(category, levels = points_sys$category)) %>%
  arrange(grid_idx, category) %>%
  group_by(grid_idx, category) %>%
  # gluing together html labels
  mutate(cell1 = paste0(category, " (", n, "): "),
         cell2 = paste0(unlist(locs), collapse = ", "),
         both_cells = paste0("<td><nobr>", cell1, "</nobr></td>", "<td>", cell2, "</td>"),
         full_row = paste0("<tr>", both_cells, "</tr>")) %>%
  group_by(grid_idx, total_points, distinct_cat) %>%
  # collapsing into html table for each grid
  summarize(full_table = paste0('<div class="scrollable">', '<table>', paste0(full_row, collapse = ""), '</table>', '</div>')) %>%
  ungroup() %>%
  left_join(grids_nyc, ., by = c("grid_idx")) %>%
  mutate(total_points = coalesce(total_points, 0),
         distinct_cat = coalesce(distinct_cat, 0),
         full_table = coalesce(full_table, "")) %>%
  # various ranks
  mutate(percentile = ntile(total_points, 100),
         rank = rank(-total_points, ties.method = "min"),
         max_rank = max(rank)) %>%
  group_by(grid_idx) %>%
  # convert area to sq ft for text
  mutate(area_text = formattable::comma(as.numeric(area)*10.764, digits = 0)) %>%
  # final html blob
  mutate(summary = paste0(
    "<b>Block #", grid_idx, " in ", borough, " (", area_text, " sq ft)</b><br>",
    "Enclosed by ", paste0(head(unlist(streets), 6), collapse = ", "), "<br><br>",
    distinct_cat, " location types, ", total_points, " total points (", toOrdinal::toOrdinal(rank), " out of ", max_rank, ")<br><br>")) %>%
  mutate(popup = paste0(summary, full_table)) %>%
  arrange(desc(total_points)) %>%
  # best binning for colors
  group_by(five_plus = total_points >= 5) %>%
  mutate(points_bin = ifelse(!five_plus, 
                             as.numeric(total_points > 0), 
                             ntile(total_points, 13) + 1)) %>%
  ungroup()

colfunc <- colorRampPalette(c("gray",
                              "darkolivegreen4",
                              "lawngreen",
                              "yellow",
                              "orange",
                              "firebrick1",
                              "darkred"))

factpal <- colorFactor(colfunc(15), sort(unique(g$points_bin)))

simple_levels <- factor(c('0', '5', '10', '15', '20+'), levels =  c('0', '5', '10', '15', '20+'))
factpal2 <- colorFactor(colfunc(length(simple_levels)), simple_levels)

block_map <- leaflet() %>% 
  addProviderTiles(providers$Stadia.StamenTonerLite) %>% 
  addCircles(data = grid_point_ix %>% 
               filter(grid_idx == 15883) %>%
               slice(-22) %>% # chase dupe
               st_set_geometry("geometry_pt"),
             label = ~name, radius = 1,
             labelOptions = labelOptions(noHide = TRUE, textOnly = TRUE, offset = c(-6, 0),
                                         direction = "left",
                                         textsize = "15px"))

big_map <- leaflet(height = "700px") %>% 
  addProviderTiles(providers$Stadia.StamenTonerLite) %>% 
  # zip data for search function
  addPolygons(data = zips,
              label = ~zip, group = "hidden",
              stroke = 0, fillOpacity = 0,
              labelOptions = labelOptions(opacity = 0)) %>%
  # primary block data
  addPolygons(data = g[, ],
              popup = ~popup,
              color = ~factpal(points_bin),
              stroke = FALSE,
              fillOpacity = 0.7,
              popupOptions = popupOptions(minWidth = 500)) %>%
  # center at NYC
  setView(lng = -73.95, lat = 40.75, zoom = 13) %>%
  # settings for the zip search functionality
  addSearchFeatures(targetGroups  = 'hidden',
                    options = searchFeaturesOptions(
                      textPlaceholder = 'Search by ZIP',
                      zoom = 15,
                      collapsed = FALSE,
                      position = "topright")
  ) %>%
  addResetMapButton() %>%
  addLegend("bottomright", pal = factpal2, values = simple_levels,
            title = "total points",
            opacity = 1
  )

table <- g %>%
  # only want real blocks no huge lots
  filter(as.numeric(area)*10.764 < 2e5) %>% 
  head(10) %>% 
  st_set_geometry(NULL) %>%
  # can't remember why double quotes don't work
  mutate(full_table = str_replace_all(full_table, '"', "'")) %>%
  select(borough, streets, points = total_points, full_table) %>%
  rowwise() %>%
  mutate(streets = paste0(unlist(streets), collapse = ", ")) %>%
  ungroup() %>%
  # details element allows for accordion reveal
  mutate(points = paste0(points, "<details><summary>locations</summary>", full_table, "</details>")) %>%
  select(-full_table) %>%
  kbl(escape = FALSE) %>%
  kable_styling()

#save(table, big_map, file = "C:/Users/Walker Harrison/Documents/GitHub/website/content/posts/2024-01-13-block-draft/page_elements.Rdata")
save(table, big_map, file = "page_elements.Rdata")