Last year I got drawn into the #30DayMapChallenge and made it to day 19. I was pretty disorganized, didn’t timebox my days, and had a couple double-map days. I’m not sure how much better this year will go, but I’m going to try and keep this page updated with the maps and methods throughout the month.
30 Day Map Challenge Categories
Day 1: Points
Dot density/proportional symbol map recording parking violations in Brooklyn.
What a start! Wouldn’t be a challenge without diving into some data. I love a good dot density map, many of which have been popping up since the challenge started. As with last year, it is tough not to be inspired by the content posted from those in time zones ahead.
For today’s map, I started surfing the NYC open data portal by most recent datasets which is where I found Parking Violations Issued - Fiscal Year 2020, though I would come to find this data contains way more than that. It comprises 43 columns and 12.5 million rows, with parking violations going back way further than 2020. One thing it is lacking though, is any sort of geocoded locations. Instead we’re given “House Number”, “Street” and “StreetCode1”, “StreetCode2”, “StreetCode3”. Along with some other geo-identifying columns.
And then look a look at some rows related to location
1 2 3 4 5 6 7 8
SELECT vehiclemake, streetcode1, violationlocation, housenumber, streetname FROM parking LIMIT 5;
vehiclemake, streetcode1, violationlocation, housenumber, streetname TOYOT 57310523604 PAUL AVE DODGE 0503NULL KINGS COLLEGE AVE TOYOT 23920523505 DECATUR AVE NISSA 23920523505 DECATUR AVE FORD 05218 VAN CORTLANDT AVE
After doing some Googling, it looks like “streetcode” is a reference to the LION dataset, which contains all the streets in NYC. Downloaded that dataset (which happened to be a ArcGIS File Geodatabase), loaded it into QGIS and attempted to import into my local PostgreSQL (with the PostGIS extension of course). It failed due to the geometry containing both MultiLineString and MultiCurve shapes. No problem, ran “Multipart to Singleparts” in the QGIS processing toolbox and was off on my way.
A look at the LION street data, this time informed by a handy metadata dictionary. The dictionary let me know some things like the streetcode in the lion dataset starts with the borough code. And that “FromLeft” to “ToLeft” describe the street numbers contained on that geometry (similarly there is a “FromRight” to “ToRight”).
1 2 3 4 5 6 7 8 9
SELECTcount(*) FROM lion_single_parts; >229,208
SELECT "StreetCode", "Street", "FromLeft", "ToLeft" FROM lion_single_parts; 435290 BEACH CHANNEL DRIVE 6000161099 41599076 STREET 6900169099 439690 COOPER AVENUE 7500175099 41489071 AVENUE 7500175099 457550 NORTH CONDUIT AVENUE90001 90099
In order to figure out how to join the streetcodes between these two datasets, I started with one example from the parking dataset and kept refining the query until I found the matching street. Lets use one of the examples above, 3604 PAUL AVE with street code 57310.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
SELECT "StreetCode", "Street" FROM lion_single_parts WHERE "Street" LIKE'%PAUL AVE%';
"StreetCode", "Street" 266110 ST PAUL AVENUE 20072099 257310 PAUL AVENUE 35913599 266110 ST PAUL AVENUE 20012005 257310 PAUL AVENUE 35013589 257310 PAUL AVENUE 00 257310 PAUL AVENUE 00 257310 PAUL AVENUE 00 257310 PAUL AVENUE 00 257310 PAUL AVENUE 00 257310 PAUL AVENUE 00 257310 PAUL AVENUE 34013499 257310 PAUL AVENUE 31013399
Here we can see the code 57310 with the borough code 2 (Bronx) appended in front.
I decided I wanted to look just at Brooklyn parking violations in 2020, and made a handy table with the subset of those violations. One of the columns, “violationlocation”, included a precinct number which could be filtered on to Brooklyn precincts (between 60 and 94).
1 2 3 4 5 6 7 8
CREATE table streetcodes as SELECT streetcode1, streetcode2, streetcode3, housenumber, streetname, vehiclecolor, summonsnumber FROPM parking WHERE violationlocation >=60AND violationlocation <=94AND streetcode1 <>'0'AND streetcode2 <>'0'AND streetcode3 <>'0' AND issuedate like'%2020%';
SELECTcount(*) FROM streetcodes; >565153
Now let’s take a look at the number of violations per street.
1 2 3 4 5 6 7
SELECT streetname, st1."FromLeft", st1."ToLeft", count(*), st1.geom FROM streetcodes, lion_single_parts as st1 WHERE'30'|| streetcodes.streetcode1 = st1."StreetCode" AND housenumber ~ E'^\\d+$' AND (housenumber::integerbetween st1."FromLeft" AND "ToLeft") GROUPBY st1.geom, st1."FromLeft", st1."ToLeft", streetname ORDERBYcount(*) desc;
It’s at this point I learn my SQL browser (Dbeaver) supports exporting tables in markdown.
This makes sense, that block of 9th street is particularly busy and has a bus stops and bike lanes.
There is a problem though, I’m only going to know which stretch of street each parking violation is on. For a quick workaround I used ST_LineInterpolatePoints, using the house number as a rough proxy for distance along the street.
A bunch of fiddling later and…
the (almost) final query:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
CREATE TABLE positions AS WITH streetcode AS ( SELECTcase WHENchar_length(streetcode1) =4THEN'30'|| streetcode1 WHENchar_length(streetcode1) =5THEN'3'|| streetcode1 ENDAS streetcodes1, housenumber, vehiclecolor, summonsnumber FROM streetcodes WHERE housenumber ~ E'^\\d+$' ), pos AS ( SELECT st1.geom AS geom, st1."FromLeft", st1."ToLeft", housenumber, vehiclecolor, summonsnumber, CASEWHEN st1."ToLeft" - st1."FromLeft" =0THEN0ELSE (housenumber::float- st1."FromLeft") / (st1."ToLeft" - st1."FromLeft") ENDAS norm_dist FROM streetcode, lion_single_parts as st1 WHERE streetcodes1 = st1."StreetCode" AND (housenumber::integerbetween st1."FromLeft" AND "ToLeft") ) SELECT ST_LineInterpolatePoints(geom, norm_dist, false) AS geom, vehiclecolor, summonsnumber FROM pos;
I then opened the newly created positions table in QGIS. This resulted in a ton of points overlapping and I couldn’t quiet figure out a good way to display them. Well why not throw in some porportional symbols then?
And finally another query - grouping by the geometry of each of those points in order to get a count of overlapping points.
1 2 3 4
SELECT geom, count(*) FROM positions GROUPBY geom ORDERBYcount(*) desc;
I popped this final query into the QGIS Database Manager, threw on some styling to match the yellow of a parking violation, and there we have it. I skipped quite a few steps and directions I took while figuring this all out. If you have any questions feel free to reach out!
Day 2: Lines
Routes from subway stops to closet coffee shops, in the style of the Anthora coffee cup.
For today’s map I decided to create a cartogram of NYC using the population of each borough as the area for each polygon. After an initial sketch on paper I opened up a jupyter notebook, and started plotting away…
Who says all these hexagon day maps need to be hexbins? This map has six(ish) sides! It’s also rotated aggressively because this is how I map Prospect Park in my mind.
First time using Affinity Designer. I like it a lot, it definitely makes more sense to be using vector image software for my maps than a raster program like Photoshop. It was easier to pick up today than any of the times I’ve ever tried to use Inkscape.
Followed this great tutorial from NASA Earth Observatory on creating true color images from Landsat 8 data. Also helped to read this article on the different bands.
Day 14: Climate Change
Mockup new bike lanes around Bartel Pritchard Square.
The b99 train was added after the nightly subway shutdowns to replace the 2 train. I didn’t realize this until too far into placing the labels, otherwise I belive the A/C and b25 would have made more sense to pair. Wrote some wild queries to explore this stuff, though most I didn’t end up using. One even included HAVING.