6.15. Conclusion
As you can see, GIS is more than just about making maps; it offers us the chance to explore and explicate the state of our world not only in the past and present, but our future as well. The conclusion that we'd like you to draw is not that chaos and disaster are the eventual lot of humanity, although that may turn out to be the case. Instead, we hope that this exercise
|
Hack 77. Become a GRASS Ninja
Or, Zen and the Art of
As children playing Make Believe, my
In this hack, we'll talk about the kind of thinking it takes to become that most formidable of open source GIS users, a
GRASS ninja
. Mind you, I don't claim to be one by any stretch of the
6.16.1. Objective #1: Secure the World's Borders
The objective for this GRASS ninja-training mission is to obtain a
Well, you might think so, and if you weren't me, you might be right. But my
We found the United Nations Environmental Programme's GEO Data Portal (http://geodata.grid.unep.ch/), which
GRASS:~/hacks/world > ls -la admin98* -rw-rw-rw- 1 schuyler staff 635858 Aug 4 1999 admin98.dbf -rw-rw-rw- 1 schuyler staff 26733 Jul 23 2003 admin98.html -rw-rw-rw- 1 schuyler staff 12439792 Aug 4 1999 admin98.shp -rw-rw-rw- 1 schuyler staff 20932 Aug 4 1999 admin98.shx -rw-r--r-- 1 schuyler staff 4877493 Sep 23 14:54 admin98_po_shp.zip GRASS:~/hacks/world > v.in.ogr -o dsn=. layer=admin98 out=admin
The
Well,
v.in.ogr
chewed and puffed over the data for quite some time, importing it into GRASS's internal vector format, cleaning up the polygons, snapping vertices, eliminating redundant edges, and
Figure 6-52. Part of a first attempt to get a vector map of the world into GRASS
The attribute data was even demonstrably all there. GRASS 5.7 has a
GRASS:~ > v.info -c admin Displaying column type for database connection of field 1: INTEGERcat CHARACTERFIPS_ADMIN CHARACTERGMI_ADMIN CHARACTERADMIN_NAME CHARACTERFIPS_CNTRY CHARACTERGMI_CNTRY CHARACTERCNTRY_NAME ... GRASS:~ > echo "select CAT,FIPS_CNTRY,CNTRY_NAME,ADMIN_NAME from admin" db.select less catFIPS_CNTRYCNTRY_NAMEADMIN_NAME 1AAArubaAruba 2ACAntigua and BarbudaAntigua and Barbuda 3AFAfghanistanBadakhshan 4AFAfghanistanBadghis 5AFAfghanistanBaghlan 6AFAfghanistanBamian ... We say "SQL-like" because, by default, the actual table is stored on disk in a DBase IV .dbf file, but a vector layer can (at least in theory) be associated with a table from an actual relational database, like MySQL or PostgreSQL. The interested reader can refer to the GRASS documentation for further details. So far, so good. However, a single glance at Figure 6-52 immediately reveals that it depicts rather a few more boundaries than one is accustomed to seeing on a map of Europe. The international boundaries are there, to be sure, but so is every other first-level administrative boundary in the world, making individual countries a bit hard to pick out. This is well and good, but it also means we're not done yet.
6.16.2. Objective #2: Reunite the World's Nations
The would-be GRASS ninja is not daunted at this juncture. Surely, the ninja trainee must concur, GRASS must have some facility for merging polygons. We simply tell GRASS to merge all of the adjacent
If a ninja-in-training may be permitted one flaw, perhaps that flaw is overconfidence. After a bit of poking in the GRASS documentation, we were forced to conclude that GRASS had no such tool, per se. GRASS has a tool,
v.extract
which
However, you may have gathered that the ninja is second only to MacGyver in sheer resourcefulness. If there is a means at hand, the ninja must find some way to exploit it. First, we need a list of the countries and assorted territories involved. The FIPS code will suffice for this purpose: GRASS:~/hacks/world > echo "select fips_cntry from admin" db.select sort uniq >fips.list
We're obliged to use the hoary Unix
sort
and
uniq
commands to extract the list of unique countries, because GRASS's rudimentary SQL parser doesn't support
DISTINCT
. Now, for each country in
fips.list
, we must
v.extract
it out of
admin
, and
v.patch
it into our new layer, which we will call
borders
. We could feed the list of countries to a bit of Perl and have the Perl script generate the necessary GRASS commands for us. That bit of Perl might look as
while (<>) {
chomp;
print qq/v.extract -d in=admin out=tmp new=$. where="FIPS_CNTRY = '$_'"\n/;
print qq/v.patch -a in=tmp out=borders\n/;
}
The
-d
option to
v.extract
instructs it to dissolve internal borders between shapes.
v.extract
then
Next, the
-a
option to
v.patch
GRASS:~/hacks/world > perl gen_extract.pl <fips.list >extract.sh GRASS:~/hacks/world > less extract.sh v.extract -d in=admin out=tmp new=1 where="FIPS_CNTRY = 'AA'" v.patch -a in=tmp out=borders v.extract -d in=admin out=tmp new=2 where="FIPS_CNTRY = 'AC'" v.patch -a in=tmp out=borders v.extract -d in=admin out=tmp new=3 where="FIPS_CNTRY = 'AF'" v.patch -a in=tmp out=borders v.extract -d in=admin out=tmp new=4 where="FIPS_CNTRY = 'AG'" v.patch -a in=tmp out=borders ... GRASS:~/hacks/world > sh ./extract.sh
And then we sit back and wait, while GRASS extracts, merges, and patches every polygon belonging to each of the world's 251 countries or country-like territories. Fortunately, waiting comes easy to the would-be ninja. The would-be ninja is not even fazed by the
Some minutes later, GRASS completes its labors without complaint; however d.vect borders type=area fails to reveal the goods. Any landmass completely within a single countrysuch as the British Isleshows up, but any landmass spanning more than one countrylike, say, mainland North Americahas simply disappeared. However, d.vect borders type=boundary shows that the vectors are all there. What gives? It turns out that, had the ninja trainee paid close attention, as every ninja must, he would have noted the following warning emitted by each execution of v.patch : Patch complete. 1 files patched. Intersections at borders will have to be snapped. Lines common between files will have to be edited. The header information also may have to be edited. A message like this from GRASS is a sure sign that the vector layer in question needs a run through v.clean to fix what ails it and reassure GRASS that the polygons contained therein really are polygons. The following does the trick: GRASS:~/hacks/world > v.clean in=borders out=borders2 tool=rmdupl,snap,bpol v.clean needs a few minutes to work its magic (this is 13 megs of vector data, after all) and it won't overwrite the original layer, so we have to create a new layer called borders2 . A quick call to d.vect reveals, as shown in Figure 6-53, that the borders2 layer does indeed contain a vector map of the world's international boundaries. Figure 6-53. Part of a vector map of national boundaries successfully extracted from the first-level administrative boundaries layer
Having
GRASS:~/hacks/world > g.remove vect=borders GRASS:~/hacks/world > g.copy vect=borders2,borders GRASS:~/hacks/world > g.remove vect=borders2,tmp However, if we now run v.info on the new borders layer, to see what sort of attribute data made it through the extraction process, we are brought up a bit short: GRASS:~/hacks/world > v.info -c borders Database connection for map <borders> is not defined in DB file The answer is that no attribute data made it through. In fact, GRASS is saying that the borders layer doesn't even have an attribute table. It is as though the ninjas have successfully infiltrated the tower, only to find that the villain has absconded with the princess. Honor demands that we give chase! 6.16.3. Objective #3: Recover the Missing Plans
Fortunately, if you'll recall, in our current set up, the vector-layer attribute tables are just DBase
.dbf
files stored in the filesystem. It would be
The
admin
layer already has all the data we need, and, if you'll recall, we took care to assign category
GRASS:~/hacks/world > echo "select fips_cntry, cntry_name from admin" db.select sort uniq perl -ne 'print "$.$_"' > borders.txt
Now, that's one heck of a shell command, but let's parse it bit by bit. The
db.select
command will dump the FIPS country code and the country name of each feature, separated by a pipe, in the
admin
layer. When the list goes through
sort
and then
uniq
, we will get a list of unique country codes and
1AAAruba 2ACAntigua and Barbuda 3AFAfghanistan 4AGAlgeria ... All it needs is a header line, which we can add by loading it up in our favorite text editor, and making it look like this: CATFIPSNAME 1AAAruba 2ACAntigua and Barbuda 3AFAfghanistan 4AGAlgeria ... The upshot is that the category numbers that we gave each country's features in the extraction step are now matched up to that country's name and FIPS code in the current step. GRASS likes the column containing the category numbers to be called CAT , but that's not a problem at all. The only step that remains is to get this attribute data into an actual .dbf file and then tell GRASS about it.
There are a bunch of options out there, butremember what we said about ninjas and resourcefulnessthe
Next, we need to put the file where GRASS can find it, which is usually
$GISDBASE/$LOCATION_NAME/$MAPSET/dbf/
. Our database directory is
/home/sderle/grass
, our current location is called
global
, and our current mapset
sderle
, so, by default, GRASS looks for the DBF file in
/home/sderle/grass/global/sderle/dbf/
, although we could put it somewhere else if we really wanted to. We choose to go with the default, copy
borders.dbf
into that directory, and then
GRASS:~/hacks/world >
v.db.connect borders driver=dbf table=borders
WARNING: The table <borders> is now part of vector map <borders> and may be
deleted or overwritten by GRASS modules.
Our work here is done. v.info -c borders gives us the output we expect: GRASS:~/hacks/world > v.info -c borders Displaying column type for database connection of field 1: INTEGERCAT CHARACTERFIPS CHARACTERNAME Ordinarily, we might verify that the attribute data was correctly assigned by running d.what.vect and clicking around on the X display monitor. Another, somewhat more roundabout, way of testing this would be to pick any country (say, Spain), zoom in on that country, use d.vect to display only that country, and see what comes out: GRASS:~/hacks/world > d.erase GRASS:~/hacks/world > d.vect borders type=area where="NAME = 'Spain'" If all of Spain, and only Spain, is what shows up, then we can be more or less confident that the attribute data has come through correctly. Figure 6-54 suggests that this is indeed the case. The princess is saved, the villain in ruinsin other words, success! Figure 6-54. Spain, by way of testing the attribute data from our vector map of the world
6.16.4. The
|