This blog is about my musings and thoughts. I hope you find it useful, at most, and entertaining, at least.
So you want to make a map but have no experience in doing so and don’t know where to start? Well you’ve come to the right place! In this tutorial I will show you where to find some basic spatial data, how to use it, and some tricks when rendering it.
Firstly, we need data to make the map with. I’m going to be using the TIGER data from the US Census Bureau. They have vast troves of spatial data (among the even more vast troves of data that you normally think of when I say “census”). All of this data is organized by FIPS codes.
FIPS codes “combine” to produce longer codes for more specific things. For instance, Pennsylvania is 42 and Allegheny County is 003 in Pennsylvania, so the unique code for Allegheny County, PA is 42003. The following chart is the organization of all the data.
Taken from “TIGER Tchnical Documention, Chapter 3”:https://www.census.gov/geo/maps-data/data/pdfs/tiger/tgrshp2013/TGRSHP2013_TechDoc_Ch3.pdf
As you can see, States have counties, and counties have Census Tracts, which in turn have Block Groups. You’ll notice that School Districts, Congressional Districts, and State Legislature districts are contained within a State (which makes sense, e.g. a school district can span county and municipality lines). Note the “County Subdivisions” under Counties; this is also known as municipalities (e.g. towns, cities, townships, boroughs, &c). While Census Tracts are normally contained by municipalities, they are not always.
If codes are additive, how does one distinguish between a county subdivision and a census tract? Good question. The answer is that they’re different lengths:
So, what happens when new things are added? Great question! The Census Bureau keeps these lists in alphabetical order, so they can’t just (well, don’t want to) add it to the end of the list. What they do is skip codes between existing items. Search for “Pennsylvania” in the FIPS look up link below and you’ll notice:
So if, say, Pittmesh County, it would become 42104. If Pennsylvania then added a “Pickle County”, well, it would have to be bumped to the bottom of the list. Sorry Pickle County.
Note that “ZIP Code Tabulation Areas” is off to the side under nation. ZIP codes are rarely something you want to be doing analysis with. ZIP Codes aren’t polygons (they aren’t defined by some space on the surface of the Earth), they are instead lines representing mail routes served by a Post Office. The ZCTA files (and others that you’ll find online) try to convert them to polygons the best they can, but it’s not always possible to do with 100% accuracy. They are also at the national level because they can and do span state boundaries.
You’ll note that things like roads and railroads aren’t listed. They are normally broken down by county for ease of use, but are more-or-less lines and don’t belong in this hierarchy (or could be stuffed in at the national level, I suppose). I will note, though, the road and railroad files aren’t always 100% accurate, though I find them good-enough for everyday usage. When they aren’t, I try to see if the municipality I’m working with has their own files or search for the state’s spatial repository (Pennsylvania’s is PASDA) every state has one.
OK, so now we know how this data is organized, let’s get files! First thing is to find the FIPS code for your county. The Census provides a nice tool to get that; just select your state and then look for your county name. (There are many other tools available everywhere as well, just search if you don’t like this one). For this tutorial I’ll be focusing on Allegheny county, but feel free to use your own county when attempting this.
Let’s use the 2014 TIGER/Line Shapefiles (go to that link and then click FTP site). The TIGER page also contains information and documentation about the files and their contents if you’d like to learn more.
First let’s grab the COUNTY file. There is only one because it’s small even thought it contains every county in the union.
Once all of these have been downloaded, unzip them each into their own directory. The thing about shapefiles is that they’r not really a “file”, they’re at least 3 (geometry file, database file, and an index file. Usually there is a projection file too, because you need to know this to use it.) You need to keep all the files that appear when you move them around.
Shapefiles contain just that, shapes. They are a form of “vector” format, similar to SVG if you’re familiar with those, that lets you zoom in, rotate, and move the map without ever losing quality. How? Instead of storing a line as “There is a point at X. There is a point at X+(just a little bit). There is a point at X+(just a bit more yet)….” it stores “There is a line from X to Y” and the computer figures out how best to draw it. The other format (“There is a point…”) are known as “Raster” formats, like JPEG, GIF, PNG, and TIFF among many others are raster format’s you’ve probably used (there are jpgs and pngs on this site and don’t tell me you haven’t seen any cat gifs
(What are projections? Well, they’re how you convert points on the globe to points on a flat surface. There are many different ways to do this. WGS 84/EPSG 4326 is what’s commonly known as “GPS Coordinates”. The census data you just downloaded is in EPSG 4269 which is a more accurate version for use in the United States and Canada. These are all in degrees.
Some projections are in feet. For instance EPSG 2272 can be used in the lower half of Pennsylvania for very good accuracy. (Feet from what? The lower left corner of the state of course!) Also since it’s in feet it makes distance calculations easy!
For the rest of this tutorial you won’t need to care about any of this, QGIS will read the project file with the shape files and figure it all out. I just wanted to let you know there is a whole other (confusing) world out there.)
Download and install QGIS, which is a Free software package that is similar to ArcGIS but free, as in beer and speech. If you ever need help, QGIS has many places where you can turn for community support which I’ve always found very helpful. (If you’re a company, Commercial Support for QGIS is also available, but for you and I IRC, the mailing list, or gis.stackoverflow are adequate (Trust me, I’ve learned a ton and have gotten help solving I can’t count how many issues from them).
Once installed, open it. You should see a screen similar to the one below.
We’ll be using QGIS 2.4.0 – Chigiak for this tutorial.
To add a shapefile layer to our canvas you can click on the little V-looking-with-a-green-plus button on the left (), or you can go to Layers > Add Vector Layer in the menu. Select the .shp (the file that ends in the shp extension) from where you unzipped the COUNTIES file above. Once loaded, you should see all the counties in the country.
Using the Zoom tool () draw rectangle around the area you’re interested in. Feel free to zoom in little by little. You don’t have to find it right away.
Until you’re mostly over the county you want.
To view information about a feature, use the “Identify Features” () button and then click on the county you’re interested in.
Now you can see all the information associated with this feature. Note the “STATEFP” is 42 and “COUNTYFP” is 003. Also, the “GEOID” is 42003, which is what we expected for Allegheny County. You can look up the meaning of some of the other fields in the TIGER documentation. (Click the “Hand” tool () to deselect everything and continue to pan the map as usual.)
Now, as an exercise in being able to query and filter your data, and to remove some clutter, right-click on your layer and go to properties.
Now, click “Query Builder”, and you should be presented with a screen like this.
You’ll note that it’s displaying all the fields on the left. Select one and click “Sample”. (That’s one way just to check if a field is what you think it is. Use the “Attribute Table” I’ll show you later to do a more in-depth looking-at of the data.)
We want the “GEOID” column that contains the FIPS code, and from the sample data, you can see that those look like county FIPS codes. The filter expression language is similar to SQL, but if you don’t know SQL, that’s OK. We want the county with a GEOID of 42003, so we enter “COUNTY” = ‘42003’ (NB(Nota bene): Use double quotes for fields and single quotes for values. Failure to do so will result in errors.) You can click “Test” to see how many results your query finds and if it’s valid, useful for a quick, well, test, of your query.
In this case we want only a single result because that ID should be unique!
Click OK on the test popup and then on the main dialog to gt back to the layer properties. Then Click OK to go back to the main window.
Now, right click on the layer and go to “Zoom to Layer”.
Now, add the county subdivision you downloaded just as you did the county file originally.
So great, now our county is covered. Make sure your municipality layer is selected and then, identify a feature, just like you did for the county (“Identify Feature” only identifies features in the currently selected layer. For fun, select the county layer and click where your county should be; you’ll see it become highlighted).
If you remember, municipalities are part of counties. In that info box, you’ll notice that the “STATEFP” is 42 and “COUNTYFP” is 003. What we can do is filter the municipalities to show only the ones in our county! (With the query “STATEFP” = ‘42’ AND “COUNTYFP” = ‘003’
As you just saw, the order of the layers is the ordered rendered; those lower in the list are on the bottom. Drag the county layer above the municipality layer, and you’ll see all your municipalities be covered by the county.
What I want to do now is to make the county layer have no fill and a think blue border. Go back to the layer properties for the county, and click on the paint brush on the left side (), the second in the list, to get to the Style properties.
Now select “No Brush” for the fill. Note some of the other brush styles.
Once you’ve done that, click on the border color and change it to blue and the “Border Width” to 0.75.
Then click OK
Now add the ROADS shapefile that we downloaded.
Let’s zoom in to a place of interest; in this case, let’s do Downtown Pittsburgh.
Let’s take a quick detour and show the railroads now. Load that file.
If your computer is like mine, it picked a terrible color for them. Let’s change it to, instead of being a line, being a line with ticks like on many maps. Go back to the layer style.
For the “Symbol layer type” use the drop down to select “Marker Line” and then select “Simple Marker” in the tree to the left.
Select the “+” marker. Then, in the tree to the left, select “Marker Line” and set the interval to be 2.
OK back to the main screen.
Select the road layer, and then use the “Identify Feature” tool to select an interstate highway.
Now select a local road.
You’ll notice the “RTTYP” is “I” for the interstate and “M” for the local, or municply-owned, road.
Go to the style dialog for the road layer. At the top where the drop down reads “Single Symbol”, click it and select “Categorized”. Then for the “Column” select “RTTYP”, followed by clicking the “Classify” button under the big white area.
You’ll see the different values
Right click on each value but “I” and change it back to blue. Also, delete the first entry, the one with no value.
Now change the color of the interstate to orange and the width to 0.7. Ok back to the main dialog.
You may notice, like you can on part of 279 (currently 376), that some interstates have blue on them. This is because there are multiple shapes in the same spot with different records; in this case it’s being listed as a few different roads with different names. Life is messy, unfortunately. There are ways to merge records, but I’m not going to get into that now.
One easy method of solving an issue like this is to “Duplicate” from the context menu for the layer and show all the records you want in one style in one and the other in the other (using the delete function like earlier); since layers are rendered in order place the interstate above the local. (NB: Duplicated layers aren’t shown by default; check the checkbox next to it to show it.)
Another advantage of duplicating the layer is that we can label one layer and another (Currently QGIS has no way of only labeling certain features and not others in a layer).
Go to the properties for the layer, and click the “Label” pane (). Check “Label this layer with” and then select the “FULLNAME” field. Next, click “Rendering” and then “Label Every part of mulit-part feature”. Click OK.
Sometimes the label-placement engine is a little finicky, especially with very long roads. Try moving the map around a little (by clicking and dragging with the hand tool) to see if that’ll place labels where you want.
Now for a little exercise.
Since our tutorial deals with Pennsylvania, we’re going to use PASDA to find some additional information. Every state has a spatial data repository. Find yours; find some data; make some cool maps!
Once Added, go to the Style dialog to a green; something that says “park”! (Also, while you’r mucking around with styles, go to the style dialog for the municipalities and make it “No Brush”, just like you did for the county.
The City of Pittsburgh’s GIS team in city planning has the best “water” file I’ve seen. I honestly find it very difficult to get good GIS info on rivers. If you know where to find it, please tell me! Anyway, download and add the water file as before; and make it a nice water-blue.
Go back to PASDA and download the “pools”: file for Allegheny County.
It’ll be a bit lack luster when we add it. Go to the style dialog and set a “Centroid Fill” for the “Symbol layer type”.
Then set the “Symbol layer type” to “SVG Fill”.
Finally, select the “Swimming” icon from “gpsicons” from the really nice library of icons QGIS comes with. In order to see the icon better, set it’s size to 8.
Click OK and admire your work!
In the next installment, I’ll show you how to use the print composer to make nice printouts of your maps, and go over some more data querying.
I have recently become involved with Pittsburghers for Public Transit where I’ve volunteered to help make maps and do spatial and socio-economic analysis to help them better understand the problems areas face. One of the first things I did, part as a demo and part to help provide a little more information for their Baldwin campaign. I will cover importing and using Census data and importing it and TIGER data in a later entry; here I want to focus on topographic analysis done with part of the TIGER data and PAT‘s GTFS data to identify area that are further than a given threshold from a public transit stop.
The first method I tried was to simply draw buffers, everything within a radius of a point, around each bus stop. Since buffers don’t consider anything but way-the-crow-flies (geodesic) distance they included parts of communities that can’t be accessed in anywhere near that distance. Take the following two maps (roads in orange, busstops as red dots); the first shows the geodesic distance from the closest busstop to a road. The second shows the shorted on-the-road (topographic) distance to it. An additional point, the space between the between the road and the busstop is not walkable (too steep), moreover, people should not have to walk through muck to get to their bus.
What I needed was a way to do Topographic or Network instead of straight geodetic analysis. I found that GRASS GIS is capable of doing the analysis I needed, and decided to give it a shot. I followed a tutorial, Basic Network Analysis With GRASS that was very close to what I wanted to be able to do. However, I kept getting nonsensical answers.
I eventually realized that my road map had very large segments for each road. In essence, the algorithm would think that the road it was on was very long, and all of the roads connected to it were too, so none of them were under the length limits I was looking for. To fix this issue, I split all of the roads into smaller parts. How I did this, and then how the analysis was performed is as follows.
TIGER data is projected in EPSG 4269 (for some explanation as to why, see this comment on StackOverflow. This projection measures distance in units of arc, which isn’t the most intuitive unit to work in for the type of analysis we want to do (Is walking 13-arcseconds (0.0036 degrees) a short distance? (It’s a quarter mile on the equator)). In order to just ignore all of that, I decided to use the EPSG 2272 (Pennsylvania South (ftUS)) project.
This is known as a “State Plane” and is used by most state agencies in the southern part of Pennsylvania (where Allegheny County is). The project is in feet, therefore distances can be measured in a straight-forward manner.
createdb topotest echo "CREATE EXTENSION postgis;" | psql topotest
First let’s grab the TIGER road map for Allegheny County (FIPS code 42003 (42 is Pennsylvania, 003 is the county in the state.)) (Fun fact, FIPS codes are in alphabetical order and often skip codes so that new ones may be added without having to lose that order or re-arrange them).
mkdir geodata cd geodata wget ftp://ftp2.census.gov/geo/tiger/TIGER2014/ROADS/tl_2014_42003_roads.zip mkdir tl_2014_42003_roads unzip -d tl_2014_42003_roads tl_2014_42003_roads.zip shp2pgsql -s 4269:2272 -W LATIN1 -I tl_2014_42003_roads.shp > tl_2014_42003_roads.sql cat tl_2014_42003_roads.sql | psql topotest
-s 4269:2272 tells it to convert from the input SRID to the output SRID (in this case the NAD-83 projection to Pennsylvania-South).
-W LATIN1 defines the encoding that the text in the shapefile’s database is in, and
-I tells it to create spatial indices.
cd .. to get back to the geodata directory.
Download the zip from the Port Authority (click “Download Port Authority GTFS files “).
mkdir pat_gtfs unzip -d ~/geodata/pat_gtfs ~/Downloads/general_transit_CleverTripID_1409.zip
To import this data into our database, we’re going to use my fork of gtfs_SQL_importer originally from cbick as his appears abandoned and some bugs needed fixed for newer versions of PostGIS. This, boys and girls, is one of the main reasons Free Software is great!
git clone https://github.com/jimktrains/gtfs_SQL_importer.git
cat gtfs_tables.sql | psql topotest
python import_gtfs_to_sql.py ~/geodata/pat_gtfs | psql topotest
cat gtfs_tables_makespatial.sql | psql topotest
cat gtfs_tables_makeindexes.sql | psql topotest
cat vacuumer.sql | psql topotest
psql topotest to login to your database and get a shell.
ALTER TABLE gtfs_stops ALTER COLUMN the_geom TYPE Geometry(Point, 2272) USING ST_Transform(the_geom, 2272);
ALTER TABLE gtfs_shape_geoms ALTER COLUMN the_geom TYPE Geometry(LineString, 2272) USING ST_Transform(the_geom, 2272);
Now to break apart the road segments into smaller segments; what the following query does is read each pair of successive segments and creates records with only that simple line and the reset of the road’s metadata.
CREATE TABLE roads AS ( SELECT gid, linearid, fullname, rttyp, mtfcc, geom, ST_Length(geom) as length FROM ( SELECT gid, linearid, fullname, rttyp, mtfcc, ST_MakeLine(sp, ep) AS geom FROM ( SELECT gid, linearid, fullname, rttyp, mtfcc, ST_PointN(geom, generate_series(1, ST_NPoints(geom)-1)) as sp, ST_PointN(geom, generate_series(2, ST_NPoints(geom) )) as ep FROM ( SELECT gid, linearid, fullname, rttyp, mtfcc, (ST_Dump(geom)).geom AS geom FROM tl_2014_42003_roads ) AS lines ) AS segments ) AS mostly_there
CREATE INDEX roads_geom_idx ON roads USING GIST (geom);
CREATE INDEX roads_gid_idx ON roads (gid);
CREATE INDEX roads_linearid_idx ON roads (linearid);
ALTER TABLE roads ADD COLUMN id BIGSERIAL NOT NULL PRIMARY KEY;
SELECT UpdateGeometrySRID(‘roads’, ‘geom’, 2272);
GRASS can be controlled fully via a command line or via the menus. If you’re new to GRASS, I would suggesst hunting around in the menus to see what features it offers. In a very helpful gesture, each menu option provides the name of the command it’s an interface to.
First, we’ll import the vector data (File > Import Vector Data > Common Input Formats [v.in.ogr]) GRASS can connect directly to PostgreSQL databases, which makes it very easy to import the data already in our database.
v.in.ogr dsn=PG:dbname=topotest layer=roads output=roads
v.in.ogr dsn=PG:dbname=topotest layer=gtfs_stops output=stops
Once loaded, the busstop layer needs to be connected to the road, otherwise you can’t get to them via the road! (Vector > Network analysis > Network maintenence [v.net])
v.net input=roads points=stops output=network operation=connect thresh=100
Additionally, lets add the stop metadata as part of the network. (Database > Vector database connections > Set vector map – database connection [v.db.connect])
v.db.connect map=network table=stops layer=2
Now for the guts of the analysis. We need to categorize all of the roads according to how far away the nearest busstop is. These are called isolines. (Vector > Network analysis > Split net [v.net.iso])
v.net.iso --overwrite input=network output=network_isolines ccats=1-10 costs=1320,2640,3960,5280,6600,7920,9240,10560 afcolumn=LENGTH abcolumn=LENGTH
The astute reader would notice two things:
The reason we’re only using 10 busstops is because running the entire network takes roughly 6.5hrs on my laptop. For testing purposes, the more-or-less instant gratification that 10 stops will provide suffices.
If we assign the iso-lines random colors with
d.vect -c network_isolines, we can get a quick glimpse of the output. (In quarter mile increments: grey, light red, dark red, green, dark green, blue, yellow, dark yellow).
As you can see, the little area we were looking at above is dark-yellow, indicating that it is in the > 2mi isoline. Now, this isn’t a very pretty visualization. Let’s export it to a shapefile and load it into QGIS. Once loaded, we’ll categorize the layer with the `cat` field and use a color ramp to show differences in distance.
Zooming in we can look at the area we’ve been focusing on.
Zooming out we can look at the entire county.
It’s plain to see that the transit network covers only the most densely populated area, visually identifiable by a high concentration of roads. However, it does not service all of these area. Further comparison with Census data would yield better results.
There are many things I would like to do with this data, but one of the firsts would be to incorporate DEM into the model so-as to take grades (which we have a lot of over in Pittsburgh) into the cost calculation.
Also, I need to ensure that bridges don’t become intersections, as that introduces a source of error into the calculations.
Another piece of information is to use the GTFS data we’ve already loaded to come up with frequencies. That would enable us to look for areas that are, for instances, not within walking distance of a bus that comes less frequently than every half-an-hour.
I have an OpenVZ box that’s always been nothing but trouble to me. I’m not sure why I keep it around, but I do. One day, ssh-add started echoing my password to the terminal. I was sad. I then tried to ssh and just kept getting “Host key verification failed.” What’s up with that?
Eventually through the use of
ssh -v -v -v I figured out that
/dev/tty wasn’t usable. (WHAT?) I
ls -l /dev/tty and found it had permissions of
crw------- owned by
root:root. I did
chmod a+rw and everything started to work.
I’m still trying to ascertain why that happened, but I wonder if openssh could provide an error if it can’t confirm a host key via input and so that
ssh-add doesn’t echo passwords;
sudo doesn’t, so I’m curious if the other methods possible on linux could be used on other systems or not. Anyway, off to #openssh to see if they have any input.
I just wanted to put this up so that if anyone else has this issue they can be guided to a (temporary?) solution. I’ll attempt to keep this post up-to-date as I learn anything.
I went back to school for Transportation Engineering because I feel that by making places more accessible, I can help improve people’s lives. One such way I feel that I can help, is by advocating for safe conditions for pedestrians. In one of my first attempts to use what I’ve learned, I’ve written a letter to my city council representetive:
On my walks to work, I’ve noticed that a bunch of intersections on Centre Ave and Baum Blvd do not have clear-times (the sum of the Yellow and All-Red phases at a traffic signal) that are sufficent to allow pedestrians to cross safely. Numerous times I’ve been caught in the middle of one of these roads, and I’m a quick walk — doubly so when comparied to an elderly or disabled person.
The MUTCD &sec; 4D.04.04.A.3 states that “Pedestrians facing a CIRCULAR GREEN signal indication, unless otherwise directed by a pedestrian signal indication or other traffic control device, are permitted to proceed across the roadway within any marked or unmarked associated crosswalk.”
The MUTCD &sec; 4D.04.03.B.3 states that “Pedestrians facing a steady CIRCULAR YELLOW or YELLOW ARROW signal indication, unless otherwise directed by a pedestrian signal indication or other traffic control device shall not start to cross the roadway.”
My measurements are as follows
Intersection Clearance Time Crosswalk length Suggested Safe Crossing Time Centre @ Liberty 6 s 55ft 16s Liberty @ Center 6 s 57ft 17s Centre @ Graham 6 s 30ft 9s Graham @ Centre 6 s 30ft 9s Baum @ Graham 6 s 60ft 18s Graham @ Baum 6 s 80ft 22s Baum @ Aiken 6 s 55ft 16s Aiken @ Baum 6 s 60ft 18s Baum @ Liberty 6 s 70ft 20s Liberty @ Baum 6 s 86ft 25s
None of these intersections are anywhere near the amount of time required to cross them by a healthy adult, let alone an elderly or disabled person. Especially given that efforts are being done on Baum Blvd to install microwave sensors to aid traffic flow, steps should be taken to ensure that pedestrians are given the proper amount of time when crossing.
Installing Pedestrian Signals (PedHeads), would provide feedback to pedestrians as to the amount of time remaining for them to begin safely crossing. This option has no affect on traffic or intersection geometry and doesn’t require retiring the signaling system. I would strongly urge you to have Public Works audit this corridor in order to determine the exact need and configuration of pedestrian signals.
This corridor is also home to a hospital and two hotels which attract many non-locals, who are not familiar with the configuration of these intersection. Even as a local, I find myself many times a week caught in the middle of the road needing to sprint to exit the roadway after opposing traffic is given a green light.
Additionally, Liberty at Baum has a leg of the intersection closed to pedestrian crossings. I believe that this is a terrible thing to do, not the least of which is because pedestrians have a habit of crossing at intersections regardless of being told not to. Additionally, to legally cross this intersection now requires waiting through 2 additional phases of the traffic signal — adding a noticeable amount of time to someone just wanting to stroll to a diner or restaurant a little further down the block. Moreover, it is extremely rude and unfriendly to pedestrians, who may be visitors to our city, to tell them that an intersection that is visibly clear and poses no immediate threat, is closed — it reflects very poorly on city trying to build a pedestrian and bike friendly image. I would also urge you to have Public Works consider removing this closure, especially if PedHeads are to be installed as it will provide reliable feedback to pedestrians on how long they have left to cross this intersection.
Thank you for your time regarding this issue. I believe that little things such as providing pedestrians reliable information on how long they have to cross an intersection go a very long way in increasing the safety, usability, and pleasantness of walking around.
I’ve recently been working on interfacing with APNS and have found it to be a terribly desinged API. It doesn’t feel like what I expect from an Apple product.
APNS a completely binary protocol, meaning that you can’t use any of the nice, well tested, battle hardened HTTP libraries out there (like you can with GCM). Being a binary protocol in-and-of-itself isn’t that bad of a thing, but it does feel awkward for the decision to be made to transport JSON via a binary protocol in 2009.
The worst part is that it’s not synchronous. You’re expected to throw packets at the service until the service kills the connection, possibly sending you an error packet before it does so. For the feedback services, you’re expected to just listen if there’s anything and disconnect. I don’t believe it would have been that difficult to, and would have the usage process much, much nicer, if the protocol would respond consitently. For instance, if I say I’m going to send 10 packets, it could wait until it’s recieved those 10 packets to send an “All Good!” or “Error: xxxx” packet. Or when connecting to get feedback, tell me that there is no feedback.
The whole system seems to be designed to be as “efficient” in terms of bandwidth as possible, but then duplications tons of fields, e.g. when setting the Device Token in a Notification packet, you need to add a Device token section that has a header and the token length, both of which are rednundant because the token is always required and always the same length. Also, being bandwidth eficent to save possibly 100s of bytes, at the expense of a crappy development experince seems very un-Apple. As much as I dislike IDEs, XCode is much much nicer of an experince to work with then Visual Studio was and the Apple Docs often feel much better than MS’s docs.
The efficiency may be warranted on the phone’s end, but a simple HTTP endpoint for sending notifications and getting feedback would have made the development process at my organization much more streamlined and quick.
APNS just feels like a quick project done by someone using vastly underpowered hardware and kind-of became a bigger thing, but that was never cleaned up. Part of me feels that it’s designed so that people refrain from using it; it feels that awkward, especially when you’re not use to dealing with raw sockets day-to-day.