Getting Started with Geographic Information Systems |
There are a number of open-source Geospatial Information System packages available. In recent years they have become powerful enough and reliable enough to do some interesting things.
One such package is GRASS, which stands for "Geographic Resources Analysis Support System”. Another is QGIS, which stands for Quantum GIS. Yet another is GDAL, which stands for Geospatial Data Abstraction Library. Here is a brief summary of the good news and bad news about these packages.
In particular, if you happen to be looking over the shoulder of an expert, you can see what commands he is using, and then look into the reference manual to find out, roughly, what those commands are supposed to do. The manual is a mapping from commands to ideas.
However, in the real world, most people cannot get started by looking over the shoulder of an expert. They start with an idea, and then they want to find out which commands, if any, allow them to carry out the idea. This requires searching through the entire manual, which is very laborious and inefficient. It is something of a chicken-and-egg problem: if you knew which command to use you could look it up, but since you don’t, you can’t.
What we need is a good tutorial. I benefitted from the data conversion and import notes at reference 1. There is also the famous newsletter article reference 2. Some documentation on the Lambert Conic Conformal Projection can be found in reference 3. There is something of a tutorial at reference 4 but it didn’t do me much good.
Figure 1 shows a shaded relief map. Esthetically speaking, I find this rather attractive. Practically speaking, it is good for portraying slope, but not particularly good at portraying height. That is, it shows you what’s steep and what’s not, but it doesn’t directly show you what’s high and what’s not.
Figure 2 shows a color-coded elevation map. This is a relatively good way of portraying height.
We can combine these two ideas as shown in figure 3.
If we are particularly interested in certain elevations, we can add contour lines, as shown in figure 4. The 3000-foot, 6000-foot, and 9000-foot levels are displayed.
We can indicate the names and locations of some of the mountain peaks, as shown in figure 5. In this diagram, the shaded relief is turned off, so we just see the color coded elevations, with no direct indication of slope. Turning off the shaded relief makes the labels easier to read.
We can plot the identifiers and locations of the local airports, along with the direct route of flight from KAVQ (Marana) to E60 (Eloy) as shown in figure 6. Also indicated by the light magenta shading is the route corridor, i.e. everything within 4 nautical miles of the route of flight. Also, the terrain is not plotted if it is below 3000 feet.
Important point: It is not necessary to show everything on a single map. One of the nice things about a GIS system is that you can conveniently turn off one layer and turn on another. This keeps the map from becoming too cluttered.
This stands in contrast to paper maps, where people often try to maximize the amount of information while minimizing the number of maps that they need to carry ... resulting in maps that are hard to interpret because they are too cluttered.
In case you were wondering, the point of the exercise is that we can plan a route that goes between the peaks rather than over them, as shown in figure 7. Also in this map, shown in blue, are the named navigation fixes known to the FAA ... plus a fix that I defined myself, namely the “pass” fix, which I use to specify the route of flight.
We begin by reviewing some basic concepts, and then gradually develop some more advanced concepts.
In cartography and geodesy, the most common coordinate system is spherical polar coordinates: latitude, longitude, and elevation.
In cartography and geodesy, the origin of latitude is conventionally the equator. The origin of longitude is usually within 100 meters or so of the Greenwich prime meridian. The choice will always involve tradeoffs and a certain amount of arbitrariness, because of various historical accidents and because of continental drift.
For some purposes it is best to measure elevation relative to the center of the earth, but more commonly it is convenient to measure it relative to some geoid. The geoid represents some notion of “mean sea level” at each point on the globe. The geoid is meant to be a gravitational isopotential surface.
In cartography and geodesy, the datum generally includes the choice of geoid, which is logical since the geoid defines the origin for elevation.
Note the following contrast:
When dealing with vector data, you don’t need a full-blown SRS. In particular you don’t need to specify any definite cartographic projection. If you know that a certain point is at such-and-such latitude, longitude, and elevation (relative to some specified datum) that’s all you need to know. That point is where it is. | When dealing with raster data, the data is essentially a flat, rectangular array of pixels. In order to know what those pixels mean, you need a way of projecting them onto the surface of the globe. Therefore, to interpret raster data you do need a full-blown SRS, including a projection. That is, you need to project the pixels onto the surface of the earth. |
Meanwhile, there is another very different, very important contrast to be made: A GIS package has both a back-end (for storing data and doing computations) and a front-end (for making maps and displaying them to humans).
| |
Various Universal Transverse Mercator (UTM) projections are widely used for representing raster data in memory and in files. This is perfectly reasonable, except at high latitudes. There are standards for this, which allow for compatibility when storing and communicating data. | There is no excuse for using any Mercator-like projection for making actual maps that will be seen by human eyes (except maybe for small areas near the equator). Just because your GIS system makes this easy to do doesn’t mean it is an acceptable practice. |
Let’s be clear: For storage and computation involving raster data, UTM is often OK. | It is also perfectly OK to do storage and computation using non-Mercator projections. For example, very nice Sectional Aeronautical Charts are available on line, as raster images using Lambert Conformal Conic projections. See reference 5. |
For storage and computation involving vector data, you don’t need any projection at all. | For making maps that will be seen by humans, Mercator projections are almost never OK. You need to find a way to reproject the data into some better-behaved projection. |
You don’t want to run around reprojecting raster data back and forth unnecessarily, because it is computationally expensive and smudges the data a little bit.
GRASS has commands to reproject raster data. That means converting data from one projection to another. An example is converting from a UTM projection to a Lambert Conformal Conic projection. You can also reproject data using the gdalwarp program provided by the GDAL package.
Using these commands is a royal pain. However, taking shortcuts is not recommended, because the tricks that are easy aren’t accurate, and the tricks that are accurate aren’t particularly easier than using the GRASS reprojection commands.
One drawback of gdalwarp is that the rasters it produces measure positions in meters relative to some weird datum, rather than using degrees of latitude and longitude. This is inconvenient when trying to query the map. Also: I don’t see why this is necessary. It should be possible to find your way around a Lambert projection using lat/lon.
This means that if you are using any reasonable projection, i.e. not Mercator, and you want to read in a kml file or anything else that uses lat/lon, GRASS insists that you switch to a UTM "location", read in the file, switch back to your original "location", and reproject the data.
The script krunch does this. It’s a mess, but since the script takes care of it, I can live with it.
QGIS knows how to reproject vector data on-the-fly, so you don’t need to mess with GRASS for this. (As discussed in section 3, vector data doesn’t really have an associated projection anyway, so technically QGIS is just projecting (not reprojecting) the data.) To make this work, you need to define a custom Coordinate Reference System for your project (menu -> Edit -> Custom CRS...). Give it a name and a definition, then push the save button (second-to-last button on the right).
Here is how to define the projection used for the examples in section 2 :
+proj=lcc +lat_1=31 +lat_2=33 +lat_0=0 +lon_0=-111
+x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs
Then go to (menu -> File -> Project Properties –> Coordinate Reference System). Check the box that says ‘Enable on-the-fly CRS transformations.’ Then go to the list-box and select the desired transformation.
There are also some options under (menu -> Edit -> Options) concerning prompting for a CRS when needed, when reading raster data. Some raster data (e.g. GTiff format) is internally tagged with metadata describing the projection it is using, but plenty of other data is not.
Also: You don’t need to fuss with reprojections when using google-earth as your frontend. It knows how to read vector data from .kml files and project it according to whatever projection it is using at the moment. The free versions of google-earth won’t read in raster data at all, so you don’t need to worry about reprojecting that.
Some things that are hard to do in GRASS are easier to do in google-earth. For example, google-earth does a lot of nice things with 3D views. The data in GRASS is of course 3D, but the viewer views it from directly above. I don’t know of any plans to support viewing at an angle.
Conversely, some things that are hard to do in google-earth are relatively easy in GRASS. Example: computing contour lines.
There are commercial versions of google-earth (starting at several hundred dollars per license) that are more capable than the freeware version. For example, you could compute a contour line in GRASS, export it, and then import it into the commercial version of google-earth.
ArcGIS is a long-established widely-used highly-capable commercial GIS package.
Mapnik is a free toolkit for developing mapping applications. See reference 6.
QGIS is free and open-source. It has a good front-end for displaying things. It has a plugin for communicating with GRASS, so you can use the GRASS back-end for computing things.
The GRASS commands to create the maps shown in section 2 are so complicated that I wrote a perl script to do the work. It is shown in krunch.
When starting GRASS, before you can do anything else, GRASS requires you to choose some GRASS "location". If there are no pre-existing locations, you are required to create one, which implies choosing a SRS (aka CRS) for the new "location". (This requirement is partially but not entirely logical, for reasons discussed in section 3.) You are forced to choose something, but the choice is not super-important, because you can change it later, using the "mapset ..." command.
Suggestion: If this is a first-time startup in an empty directory, push the EPSG button and use code 4326. That’s the EPSG identifier for UTM (Universal Transverse Mercator). That CRS is used by Shuttle Radar Terrain Mapping (SRTM) data files and lots of other things. This is a reasonable choice for data storage, communication, and computation ... but not for map-making, as discussed in section 3.
This is one of the reasons why I knew I had to write a script. The script systematically calculates the region and tells GRASS about it. Keep in mind that the GRASS “region” is a very different concept from the GRASS “location”.
There are a number of commands that will fail in the GRASS shell, including most of the commands that start with “d.” such as d.font and d.rast. However, see also next item.
This “Output - GIS.m” window is not just for output; it has a little input window near the bottom where you can type commands, rather like in the aformentioned GRASS shell, except that the display is defined in this environment, so some of the “d.” commands will work, such as d.info. Most of them still don’t work, as discussed in section 7, but at least some of them do.
For example, d.mon -L in the GRASS shell window reports that all possible monitors are “not running”. However, the same command in the “Output GIS.m” window reports them all as running.
In the examples in this document, I started with elevation data from SRTM i.e. the Shuttle Radar Topography Mission, reference 7. The script knows how to download this data in some cases.
I also used data for airports, navaids, and navigation fixes from the X-plane databases, reference 8. You need to download this by hand, if you don’t already have it lying around.
Here is a concise listing of all the commands used by the krunch script to generate the map layers used in the examples shown in section 1.
Script started on Thu 22 Sep 2011 08:13:58 PM MST + ./krunch -f : +++; touch N32W111.force : +++; mosaic='mosaic' tiles='N32W111 N32W112 N31W111 N31W112' make -f tiles.make wget -c http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America/N32W111.hgt.zip --2011-09-22 20:13:58-- http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America/N32W111.hgt.zip Resolving dds.cr.usgs.gov... 152.61.128.95 Connecting to dds.cr.usgs.gov|152.61.128.95|:80... connected. HTTP request sent, awaiting response... 200 OK The file is already fully retrieved; nothing to do. rm -f N32W111.force : +++; r.in.gdal input=mosaic.tif output=mosaic --q : +++; g.region rast=mosaic : +++; g.proj --q -c georef=mosaic.tif : +++; r.shaded.relief --q map=mosaic zmult=10 : +++; r.contour --q input=mosaic output=mosaic_con3 levels=914.4 : +++; r.contour --q input=mosaic output=mosaic_con6 levels=1828.8 : +++; r.contour --q input=mosaic output=mosaic_con9 levels=2743.2 : +++; v.out.kml --q -l mosaic_con3 size=2 color=255:255:0 /usr/bin/ogr2ogr --formats WARNING: Default driver / database set to: driver: dbf database: $GISDBASE/$LOCATION_NAME/$MAPSET/dbf/ WARNING: Vector map <mosaic_con3> is 3D. Use format specific layer creation options (parameter 'lco') to export in 3D rather than 2D (default) -f KML mosaic_con3.kml.raw mosaic_con3.sh : +++; g.mapset mapset=PERMANENT location=UTM_4326_SRTM Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history : +++; v.in.ogr --q -z dsn=some-apt.kml out=kapts WARNING: Width for column Name set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Description set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Name set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Description set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Name set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Description set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Cleaning polygons, result is not guaranteed! : +++; g.mapset mapset=PERMANENT location=z111 Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/z111/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/z111/PERMANENT/.bash_history : +++; v.proj --q location=UTM_4326_SRTM input=kapts : +++; g.mapset mapset=PERMANENT location=UTM_4326_SRTM Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history : +++; v.in.ogr --q -z dsn=some-track.kml out=ktrack WARNING: Width for column Name set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Description set to 255 (was not specified by OGR), some strings may be truncated! : +++; g.mapset mapset=PERMANENT location=z111 Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/z111/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/z111/PERMANENT/.bash_history : +++; v.proj --q location=UTM_4326_SRTM input=ktrack : +++; g.mapset mapset=PERMANENT location=UTM_4326_SRTM Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history : +++; v.in.ogr --q -z dsn=some-corridor.kml out=corridor WARNING: Width for column Name set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Description set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Cleaning polygons, result is not guaranteed! : +++; g.mapset mapset=PERMANENT location=z111 Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/z111/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/z111/PERMANENT/.bash_history : +++; v.proj --q location=UTM_4326_SRTM input=corridor : +++; g.mapset mapset=PERMANENT location=UTM_4326_SRTM Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/UTM_4326_SRTM/PERMANENT/.bash_history : +++; v.in.ogr --q -z dsn=other-corridor.kml out=othercorridor WARNING: Width for column Name set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Width for column Description set to 255 (was not specified by OGR), some strings may be truncated! WARNING: Cleaning polygons, result is not guaranteed! WARNING: 119 areas represent more (overlapping) features, because polygons overlap in input layer(s). Such areas are linked to more than 1 row in attribute table. The number of features for those areas is stored as category in layer 2 : +++; g.mapset mapset=PERMANENT location=z111 Erasing monitors... Cleaning up temporary files... WARNING: Your shell continues to use the history for the old mapset You can switch the history by commands: history -w; history -r /home/jsd/grassdata/z111/PERMANENT/.bash_history; HISTFILE=/home/jsd/grassdata/z111/PERMANENT/.bash_history : +++; v.proj --q location=UTM_4326_SRTM input=othercorridor Reprojecting primitives: Script done on Thu 22 Sep 2011 08:14:15 PM MST |
#! /usr/bin/perl -w use strict; use Symbol; use List::Util qw[min max]; use POSIX; ## for floor and ceil use File::Basename; # To do: FIXME: # r.fillnulls # Note: color=rainbow is (for now) favored. # May need to remove gdalwarp output file; see manpage. ######################################## # some constants: my $nm = 1853.248; ## nautical mile (not nanometer) my $pi = 4*atan(1); my $deg = $pi/180.; my $inch = 0.0254; ## in meters my $foot = 12*$inch; my $Q = '"'; ## double quote ########## # some global settings and mode flags: my $goodloc = 'z111'; my $recompute = 0; my $verbosity = 1; my $verboflag = '--q'; my $vbf = '-q'; # shorter version of verboflag # current bounding box, in degrees: my $left = 9e99; my $right = -9e99; my $top = -9e99; my $bot = 9e99; my $lrmid = 0; # bounding box, in meters: ##??? [not used] my ($north, $south, $east, $west); ###################################################################### # functions sub xystem { my ($cmd, $vlevel) = @_; if (! defined $vlevel) { $vlevel = 1; } if ($vlevel <= $verbosity) { print ": +++; $cmd\n"; } system $cmd; } sub g_gisenv_get { my ($var) = @_; my $cmd = "g.gisenv get=$var"; my $pipe = Symbol::gensym; open($pipe, "-|", $cmd) || die "Could not pipe from '$cmd'\n"; my $rslt = <$pipe>; chomp $rslt; close $pipe; return $rslt; } sub map_exists { my ($mapname) = @_; ## my $cmd = "g.mlist -m type=all"; my $cmd = "g.list -m type=all"; my $pipe = Symbol::gensym; open($pipe, "-|", $cmd) || die "Could not pipe from '$cmd'\n"; liner: while (my $line = <$pipe>){ chomp $line; $line =~ s'@.*''; if ($line eq $mapname) { return 1; } } return 0; } sub read_kml { my ($fname, $table) = @_; my $utm_location = "UTM_4326_SRTM"; my $loc = g_gisenv_get('LOCATION_NAME'); if ($loc ne $utm_location) { if (! -d $utm_location){ print STDERR "Location does not exist: $utm_location\n"; return; } xystem "g.mapset mapset=PERMANENT location=$utm_location"; } if (map_exists($table)) { xystem "g.remove -f $verboflag type=vector name=$table"; } #?? # set the region before doing the import: #?? xystem "g.region w=$left s=$bot e=$right n=$top"; xystem "v.in.ogr $verboflag input=$fname output=$table"; if ($verbosity >= 2) { xystem "g.proj -p"; # info about current projection xystem "g.region -p"; # basic info about region # including projection ## xystem "g.mlist -t -m type=all"; # list available map layers xystem "g.list -t -m type=all"; # list available map layers # in the format "type/layer@MAPSET" xystem "v.info map=$table"; # info about particular map } print "..............\n"; if (! -d $goodloc){ print STDERR "Location does not exist: $goodloc\n"; return; } xystem "g.mapset mapset=PERMANENT location=$goodloc"; if (map_exists($table)) { xystem "g.remove -f $verboflag type=vector name=$table"; } xystem "v.proj $verboflag location=UTM_4326_SRTM input=$table" } sub mk_contour { my ($mosaic, $suffix, $levels) = @_; my $table = "${mosaic}_$suffix"; if (map_exists($table)) { xystem "g.remove -f $verboflag type=vector name=$table"; } xystem "r.contour $verboflag input=$mosaic output=$table levels=$levels"; } # Compute an overall bounding box to cover all tiles: sub get_box_alltiles { my @tiles = @_; for my $tile (@tiles) { my $cmd = "gdalinfo -nogcp -nomd -noct $tile.hgt"; my $pipe = Symbol::gensym; open($pipe, "-|", $cmd) || die "Could not pipe from '$cmd'\n"; liner: while (my $line = <$pipe>){ chomp $line; $line =~ s'[(),]' 'g; my @stuff = split (' ', $line); if (! defined $stuff[1]) { next liner; } if ($stuff[0] eq 'Upper') { $top = max($top, $stuff[3]); } if ($stuff[0] eq 'Lower') { $bot = min($bot, $stuff[3]); } if ($stuff[1] eq 'Right') { $right = max($right, $stuff[2]); } if ($stuff[1] eq 'Left') { $left = min($left, $stuff[2]); } } } # round to nearest whole degree: $top = floor($top + .5); $bot = floor($bot + .5); $left = floor($left + .5); $right = floor($right + .5); $lrmid = ($left + $right) / 2; print "alltiles: $top > $bot ... $left < $right : $lrmid\n"; } main: { my @tiles = (); for my $arg (@ARGV) { if ($arg eq '-v') { $verbosity++; } elsif ($arg eq '-f') { $recompute++; } elsif ($arg =~ '^-') { die "Unrecognized option '$arg'\n"; } else { push @tiles, $arg; } } if (!@tiles) { @tiles = ("N32W111", "N32W112", "N31W111", "N31W112"); } my $alltiles = join(' ', @tiles); if ($verbosity > 1) { print "Tiles are: $alltiles\n"; } if ($verbosity > 2) { $verboflag = '--v'; $vbf = ''; } if (basename(getcwd) ne 'grassdata') { print STDERR "Please cd to 'grassdata'\n"; exit(1); } my $loc = g_gisenv_get('LOCATION_NAME'); if ($loc ne $goodloc) { print STDERR "Warning: Location ($loc) should be $goodloc\n"; } ## Data sources: ## http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America/N32W111.hgt.zip ## Bigger, not necessarily better: ## http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N32W112.hgt.zip my $america = "http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America"; my $eurasia = "http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/Eurasia"; my $mosaic = "mosaic"; if ($recompute) { xystem "touch $tiles[0].force"; } xystem "mosaic='$mosaic' tiles='$alltiles' make -f tiles.make"; { # read it into grass my $newloc = ''; if (-d "$goodloc/PERMANENT") { # location exists; switch to it my $loc = g_gisenv_get('LOCATION_NAME'); if ($loc ne 'z111') { xystem "g.mapset mapset=PERMANENT location=$goodloc"; } } else { # location doesn't exist; create it from file $newloc = "location=$goodloc"; } if ($recompute && map_exists($mosaic)){ xystem "g.remove -f $verboflag type=raster name=$mosaic"; } my $slurp = "r.in.gdal input=$mosaic.tif output=$mosaic"; $slurp .= " $newloc $verboflag"; if ($recompute || $newloc ne '' || !map_exists($mosaic)) { xystem "$slurp"; } # and set the region accordingly: # this is important: xystem "g.region rast=$mosaic"; # Also set the projection accordingly. # This happens automatically when the region gets created, # but not if we read a new file into an old region, so # we really need to do this explicitly. # Also note that the -c is important and non-obvious: xystem "g.proj $verboflag -c georef=$mosaic.tif"; # Create shaded-relief representation. # Note that the contour routine reports that # the initial color table is set to "grey" my $sname = "$mosaic.shade"; my $shadex = map_exists($sname); if ($recompute && $shadex) { xystem "g.remove -f $verboflag type=raster name=$sname"; $shadex = 0; } if (!$shadex) { xystem "r.shaded.relief $verboflag map=$mosaic zmult=10"; } # make kml file containing contour lines if (0) { mk_contour($mosaic, "con3", 3000*$foot); mk_contour($mosaic, "con6", 6000*$foot); mk_contour($mosaic, "con9", 9000*$foot); xystem ("v.out.kml $verboflag -l ${mosaic}_con3 size=2 color=255:255:0"); } } { # create shaded maps my $cut = 3000*$foot; xystem "r.mapcalc --overwrite $Q${mosaic}_3k " . " = if($mosaic >= $cut, $mosaic, null())$Q"; xystem "r.mapcalc --overwrite $Q${mosaic}_3k.shade " . " = if($mosaic >= $cut, ${mosaic}.shade, null())$Q"; } read_kml("some_fix.kml", "some_fix"); read_kml("some_apt.kml", "some_apts"); read_kml("some_track.kml", "some_track"); read_kml("some_corridor.kml", "some_corridor"); read_kml("direct_corridor.kml", "direct_corridor"); } |
And here is a makefile that fetches data, builds a mosaic, removes nulls, and reprojects from UTM to Lambert.
# Key variables, defined in environment: # $tiles (input), $mosaic (principal output) # Other useful variables # $projection, $verbosity, $CRS (i.e. GRASS "location") ifndef CRS CRS = UTM_4326_SRTM endif ifndef tiles tiles = N31W111 N31W112 N32W111 N32W112 endif ifndef projection projection = $(shell ./gis-bbox $(tiles)) endif # for reference: lcc_projection = +proj=lcc +lat_1=31 +lat_2=33 +lat_0=0 +lon_0=-111 \ +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs ifndef mosaic mosaic = mosaic endif m = $(mosaic) ifndef verbosity verbosity = -q endif america = http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America eurasia = http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/Eurasia # see if location already exists; # if not, construct phrase for creating it: setloc = $(shell if ! test -d $(CRS)/PERMANENT ; then \ echo location=$(CRS) ; fi) hgt_files = $(tiles:%=%.hgt) zip_files = $(hgt_files:%=%.zip) tif_files = $(tiles:%=%.tif) force_files = $(tiles:%=%.force) %.tif : %.hgt gdalwarp $(verbosity) -of GTiff -s_srs EPSG:4326 \ -t_srs EPSG:4326 $< $@ #? rm $< # .hgt file no longer needed %.hgt : %.hgt.zip unzip -o $< touch -r $@ $@.creation_time touch $@ # time of unzipping, not time of creation #? rm $< # .zip file no longer needed # FIXME: should check whether america or eurasia: %.hgt.zip : %.force wget -c $(america)/$@ rm -f $< .SECONDARY : $(hgt_files) $(zip_files) $(force_files) .PHONY : all all : $(mosaic).tif $(mosaic)_utm.tif : $(tif_files) gdalwarp $(verbosity) -of GTiff -s_srs EPSG:4326 \ -t_srs EPSG:4326 $^ $@ $(mosaic)_filled.tif : $(mosaic)_utm.tif if test -z "$(newloca)" \ -a "_$$(g.gisenv get=LOCATION_NAME)" != "_$(CRS)" ; then \ g.mapset mapset=PERMANENT location=$(CRS) ; fi r.in.gdal $(verboflag) input=$< output=$(mosaic)_utm \ $(setloc) --overwrite g.region rast=$(mosaic)_utm r.mapcalc "$m_utm = if($m_utm == 0, null(), $m_utm)" r.fillnulls input=$(mosaic)_utm output=$(mosaic)_filled --overwrite r.out.gdal format=GTiff input=$(mosaic)_filled output=$@ $(mosaic).tif : $(mosaic)_filled.tif gdalwarp $(verbosity) -of GTiff -s_srs EPSG:4326 \ -t_srs '$(projection)' \ $^ $@ |
// Parse fix.dat and apt.dat files // select the fixes of interest // calculate track (leg course and distance ... and corridor) // and format the results. // To do: // *) http://code.google.com/apis/kml/documentation/kml_tut.html // *) use distance to TRACK rather than distance to target points // *) clean up box selector // -- parse 12e 34w // -- swap depending on which is left, which is right // *) seen_it would be better as a list of Features (not strings). // *) use actual commas (and quotes) in .csv output // *) better color map? custom rules file? // *) Get rid of restrictors; // it's easier and much quicker to write all file-types every time. // *) 3D corridor, at altitude. using namespace std; #include <iostream> #include <fstream> #include <GeographicLib/Geodesic.hpp> #include <cmath> #include <stdio.h> /* for popen() */ #include <sstream> #include <algorithm> /* for sort */ #include <vector> #include <set> #include <map> #include <iomanip> /* for setw */ #include <sys/types.h> /* for stat */ #include <sys/stat.h> #include <unistd.h> using namespace GeographicLib; double a = Constants::WGS84_a<double>(); double f = Constants::WGS84_f<double>(); const Geodesic geod(a, f); #include "GeoPoint.h" static const double nm(1853.248); // nautical mile (not nanometer) static const double pi(M_PI); static const double deg(pi/180.); static const double inch(0.0254); // measured in SI meters static const double foot(12.*inch); static const double bogus(-9e99); #define X(aaa) aaa, enum catter { #include "catter.h" }; #undef X map<catter,string> catter_name; #define X(aaa) catter_name[aaa] = #aaa; void init_catter_names() { #include "catter.h" } #undef X class Runway{ public: GeoPoint end[2]; double width; // in meters int sfc; }; class Feature { public: string ident, fullname; GeoPoint loc; double dist; double fwd; // in radians int has_tower; vector<Runway> rwy; catter cat; // Note that category code "feature" indicates // a generic feature with no definite category. // constructors: Feature(const string _ident, const double _lat, const double _lon) : ident(_ident), fullname(""), loc(_lat, _lon, -9e99), has_tower(0), cat(feature) {} Feature() : ident("x-x"), fullname(""), loc(0, 0, -9399), has_tower(0), cat(phantom) {} }; typedef vector<Feature> VP; // A Bucket is list of Features ... all with the same ident! // This is necessary, because fix names are not unique. // Here are the multiplicity numbers for some examples: // 9 DELTA // 8 TANGO // 8 BRAVO // 7 TOMAS // 7 FRANK // 7 CORAL // 7 ALPHA // 6 STONE // 6 ROCKY // 6 ROBIN // 6 GOLFO // 6 CRANE // 6 CANDY // 6 APPLE // // VOR names are similarly non-unique. // 5 SIE // 4 TRN // 4 TAN // 4 SAV // 4 SAM // 4 MGA // 4 CMA // 4 BNA // 4 BAL // // Whenever a non-unique name is mentioned, we go to a lot // of trouble to find the one that is nearest the previous fix. class Bucket : public VP { public: Feature first_for_debugging_only() const { Feature rslt; if (size() == 1) { return front(); } if (!size()) { cerr << "? How did we get a bucket of zero size in the dbase?" << endl; rslt.cat = phantom; return rslt; } /// if (size() > 1) {...} { // must be ambiguous if (0) cerr << "? Identifier '" << front().ident << "' is " << size() << "-fold ambiguous." << endl; rslt.cat = ambiguous; rslt.has_tower = size(); return rslt; } } Feature nearest_to(const Feature& vicinity) const { Feature rslt; if (size() == 1) { return front(); } if (size() == 0) { cerr << "? How did we get a bucket of zero size in the dbase?" << endl; rslt.cat = phantom; return rslt; } if (vicinity.cat == phantom){ // ambiguous and not fixable // don't print error message; let caller do it: if (0) cerr << "? Identifier '" << front().ident << "' is " << size() << "-fold ambiguous." << endl; rslt.cat = ambiguous; rslt.has_tower = size(); return rslt; } double dist_min(9e99), fwd, rev; for (VP::const_iterator ptr = begin(); ptr != end(); ptr++) { double dist = vicinity.loc.geoDistTo(ptr->loc, fwd, rev); if (dist < dist_min) { dist_min = dist; rslt = *ptr; if (0) cerr << "Hit at " << dist / nm << " vlat : " << vicinity.loc.latRad/deg << " vlon : " << vicinity.loc.lonRad/deg << endl; } } return rslt; } }; ////////////////////////////////////////////////////////////////////// // various useful functions // remove \n or \r\n from the end of a string: string chomp(const string sss){ string rslt(sss); int where; where = rslt.length(); if (where > 0) { where--; if (rslt[where] == '\n') rslt = rslt.substr(0, where); } where = rslt.length(); if (where > 0) { where--; if (rslt[where] == '\r') rslt = rslt.substr(0, where); } return rslt; } string ltrim(const string sss){ string rslt(sss); size_t where = rslt.find_first_not_of(" \t"); if (where == string::npos) return rslt; return rslt.substr(where); } // Compare two featuress according to their "dist" // field, which presumably indicates how close they // are to some reference point. class distcomp{ int origin; public: bool operator()(const Feature& a, const Feature& b) const{ return a.dist < b.dist; } distcomp(int _origin) : origin(_origin) { // planning for the future } }; string toupper(const string sss){ string rslt(sss); for (string::iterator ptr=rslt.begin(); ptr != rslt.end(); ptr++){ *ptr = toupper(*ptr); } return rslt; } class CaseFold{ public: int operator() (const string& a, const string& b){ return toupper(a) < toupper(b); } }; FILE* open_fix_file(const string _fn, const int oknone=0){ string fn(_fn); string tested(""); struct stat statbuf; // This stat business is important, because it is hard // to catch errors in the zcat command in the pipe. // Also this makes it easy to add an implicit ".gz" if required. int rslt = stat(fn.c_str(), &statbuf); tested = fn; if (rslt < 0) { if (fn.find(".gz")+3 == fn.length()) { // already an explicit ".gz" } else { fn += ".gz"; rslt = stat(fn.c_str(), &statbuf); tested += " " + fn; } } if (rslt < 0) { if (oknone) return 0; cerr << "Could not stat file(s) '" << fn << "'\n" << endl; exit(1); } FILE* pipe(0); if (fn.find(".gz")+3 == fn.length()) { pipe = popen(("zcat " + fn).c_str(), "r"); } else { pipe = fopen((fn).c_str(), "r"); } if (!pipe) { cerr << "Could not open input file '" << fn << "'\n"; exit(1); } return pipe; } // Fix file version 601 is the same as version 600 // with the _elevation_ inserted as the third field // void scan_fixes(const string fn, const int oknone, vector<Feature>& foo, const catter setcat = fix){ FILE* pipe = open_fix_file(fn, oknone); if (!pipe) return; size_t size(100); char* buf = (char*)malloc(size); if (getline(&buf, &size, pipe) < 0) { cerr << "Oops.\n"; exit(1); // can happen on file-not-found } string sbuff(chomp(buf)); if (sbuff != "I") { cerr << "Unexpected format in fix file '" << fn << "'" << endl; exit(1); } // read second line: if (getline(&buf, &size, pipe) < 0) { cerr << "Can't read second line.\n"; exit(1); } int fileversion(0); sscanf(buf, "%d", &fileversion); if (fileversion != 600 && fileversion != 601) { cerr << "Unexpected version: " << fileversion << " in fix file '" << fn << "'" << endl; cerr << "Line was: " << chomp(buf) << endl; exit(1); } // skip rest of header, // i.e. scan for empty line at end of header: for (;;) { if (getline(&buf, &size, pipe) < 0) { break; } if (chomp(buf).length() == 0) break; } Feature ppp; for (;;){ if (getline(&buf, &size, pipe) < 0) { break; } //fprintf (stdout, "%s...\n", buf); stringstream parse(buf); string tmp; parse >> tmp; tmp = ltrim(tmp); // Can happen at end of file: if (tmp.length() == 0) continue; if (tmp[0] == '#') continue; ppp.loc.latRad = atof(tmp.c_str()) * deg; parse >> tmp; ppp.loc.lonRad = atof(tmp.c_str()) * deg; if (fileversion == 601) { parse >> ppp.loc.elev; ppp.loc.elev *= foot; // convert from feet to meters } parse >> ppp.ident; ppp.cat = setcat; foo.push_back(ppp); } fclose(pipe); } // Declare this locally, because // it adds things in Mercator space, // which is not safe unless things are very nearby. // It's OK for present purposes, though. GeoPoint& operator+=(GeoPoint& a, const GeoPoint& b){ a.latRad += b.latRad; a.lonRad += b.lonRad; return a; } GeoPoint& operator/=(GeoPoint& a, const double denom){ a.latRad /= denom; a.lonRad /= denom; return a; } void scan_apts(const string fn, const int oknone, vector<Feature>& foo){ FILE* pipe = open_fix_file(fn, oknone); if (!pipe) return; size_t size(100); char* buf = (char*)malloc(size); getline(&buf, &size, pipe); string sbuff(chomp(buf)); if (sbuff != "I") { cerr << "Unexpected format in airport file '" << fn << "'" << endl; exit(1); } // read second line: if (getline(&buf, &size, pipe) < 0) { cerr << "Can't read second line.\n"; exit(1); } int fileversion(0); sscanf(buf, "%d", &fileversion); if (fileversion != 810 && fileversion != 850) { cerr << "Unexpected version: " << fileversion << " in apt file '" << fn << "'" << endl; cerr << "Line was: " << chomp(buf) << endl; exit(1); } // skip rest of header, // i.e. scan for empty line at end of header: for (;;) { if (getline(&buf, &size, pipe) < 0) { break; } if (chomp(buf).length() == 0) break; } // for 810 double lat_deg, lon_deg, elev_foot; string fullname, rwylabel; double heading, rwylength; double displaced, stopway; int bldgs; int lighting_code; int shoulderType; double smoothness; int centerLights, edgeLights, distanceSigns; // 850, each end of the runway: double wid_ft, overrun; int markingType, approachLights, tdzLights, REIL; int phase(0); // 0 means pre-record, looking for record // 1 means we have started but not finished // processing a record. GeoPoint rwyAccum; int nRwyEnds; Feature ppp; for (;;){ // loop over all rows in file if (getline(&buf, &size, pipe) < 0) { break; } stringstream parse(chomp(buf)); int rowtype; parse >> rowtype; if (rowtype == 1) { if (phase > 0) { foo.push_back(ppp); } ppp = Feature(); // start a new Feature parse >> elev_foot; parse >> ppp.has_tower; parse >> bldgs; parse >> ppp.ident; getline(parse, fullname); // trim off leading spaces: while (fullname.length() && fullname[0] == ' ') fullname = fullname.substr(1); ppp.fullname = fullname; ppp.cat = apt; rwyAccum = GeoPoint(0,0); nRwyEnds = 0; phase = 1; } else if (rowtype == 10) { // occurs in fileversion 810 // inherited by 850 but used only for taxiways if (phase != 1) { // should never happen cerr << "Phase error; bad format in file '" << fn << "'" << endl; exit(1); } Runway rwy; parse >> lat_deg; parse >> lon_deg; parse >> rwylabel; if (rwylabel == "xxx") continue; // indicates a taxiway parse >> heading; parse >> rwylength; parse >> displaced; parse >> stopway; parse >> wid_ft; // width in feet parse >> lighting_code; parse >> rwy.sfc; GeoPoint center(lat_deg*deg, lon_deg*deg); GeoPoint firstend = center.displace(heading*deg, rwylength*foot/2.); rwyAccum += firstend; nRwyEnds++; GeoPoint otherend = center.displace(pi + heading*deg, rwylength*foot/2.); rwyAccum += otherend; nRwyEnds++; ppp.loc = rwyAccum; ppp.loc /= nRwyEnds; ppp.loc.elev = elev_foot*foot; rwy.end[0] = firstend; rwy.end[1] = otherend; rwy.width = wid_ft * foot; ppp.rwy.push_back(rwy); } else if (rowtype == 100) { // occurs in fileversion 850 if (phase != 1) { // should never happen cerr << "Phase error; bad format in file '" << fn << "'" << endl; exit(1); } Runway rwy; parse >> rwy.width; // in meters parse >> rwy.sfc; parse >> shoulderType; parse >> smoothness; parse >> centerLights; parse >> edgeLights; parse >> distanceSigns; for (int rend = 0; rend < 2; rend++){ parse >> rwylabel; parse >> lat_deg; parse >> lon_deg; //... parse >> displaced; parse >> overrun; parse >> markingType; parse >> approachLights; parse >> tdzLights; parse >> REIL; rwyAccum += rwy.end[rend] = GeoPoint(lat_deg*deg, lon_deg*deg); nRwyEnds++; ppp.loc = rwyAccum; ppp.loc /= nRwyEnds; ppp.loc.elev = elev_foot*foot; } ppp.rwy.push_back(rwy); // that's all for this type-100 runway-record } else { // ignore other record types } } // end of file if (phase > 0) foo.push_back(ppp); fclose(pipe); } int prefix(const string shorter, const string longer){ return shorter == longer.substr(0, shorter.length()); } // returns the difference in the range -180 to +180. double diffangle(const double a, const double b){ double rslt(a-b); rslt = fmod(rslt, 360.); if (rslt > 180.) rslt -= 360.; return rslt; } void usage() { cerr << "Usage: getafix [options] TARGET\n" "Options include:\n" " Selectors:\n" " -range R Show all fixes within a radius R.\n" " -count N Show the nearest N fixes.\n" " -box x1 y1 x2 y2 Show all fixes in box with corners (x1,y1) (x2,y2)\n" " [all specified in degrees]\n" " Restrictors:\n" " -azimuth A W Limit radial search to direction A +- W [in degrees]\n" " -apt Show only records of type 'apt'\n" " -fix Show only records of type 'fix'\n" " -obstruction Show only records of type 'obst'\n" " -duplicates Include duplicate records in output\n" " Special features:\n" " -track Ignore selectors;\n" " just spit out the lat/lon of the targets\n" " along with the true course and distance\n" " (and reverse course) to (from) the next target.\n" " -kml ????\n" " -paved ????\n" " Settings and modes:\n" " -corridor hwid In track mode: halfwidth of corridor,\n" " i.e. margin on each side of centerline.\n" " Default=4 is relevant to FAR 91.177.\n" " -meters Output all distances in meters\n" " (as opposed to some in nm).\n" " -near ident Locate first target in this vicinity if there is ambiguity.\n" "\n" "Notes: *) On the cmd line, all distances are in nautical miles.\n" " *) On the cmd line, bearings are in degrees, CW from true north.\n" " *) Selectors are cumulative; e.g. specifying a range and a\n" " box gives you everything within that range plus everything\n" " within that box.\n" "\n" "On the command line, each TARGET or ident must already be in the database;\n" "there is not yet any provision for entering lat/lon on the fly.\n" "Your best bet is to edit ./fix2.dat and define suitable fixes.\n" "\n" ; } // some globals ... mostly mode settings double az_center; double az_width(360.); double track_elevation(0); int elevation_relative(1); int track_mode(0); int kml_mode(0); int need_paved(0); double corr_hwid(4. * nm); int include_dupes(0); double max_range(0); unsigned int max_count(0); catter need_cat(feature); double output_unit(nm); string vicinity_name(""); GeoPoint box1(9e99, 9e99), box2(9e99, 9e99); string outfile_prefix("some"); string protect(const string xxx){ string rslt; for (string::const_iterator ptr = xxx.begin(); ptr != xxx.end(); ptr++){ char ch = *ptr; if (ch == '&') rslt += "&"; else rslt += ch; } return rslt; } string fixup(const string xxx){ string rslt(xxx); for (string::iterator ptr = rslt.begin(); ptr != rslt.end(); ptr++){ if (*ptr == '$') *ptr = '"'; } return rslt; } string kml_header(fixup( "<?xml version=$1.0$ encoding=$UTF-8$?>\n" "<kml xmlns=$http://www.opengis.net/kml/2.2$>\n" "<Document>\n" )); string kml_trailer(fixup( "</Document>\n" "</kml>\n" )); string kml_styleblock(fixup( " <Style id=$fat-blue$>\n" " <LineStyle>\n" " <color>ffFF0000</color>\n" " <width>4</width>\n" " </LineStyle>\n" " </Style>\n" "\n" // track centerline " <Style id=$track$>\n" " <LineStyle>\n" " <color>ff0000FF</color>\n" " <width>4</width>\n" " </LineStyle>\n" " </Style>\n" "\n" // route corridors: " <Style id=$corridor$>\n" " <LineStyle>\n" " <color>000000FF</color>\n" " <width>0</width>\n" " </LineStyle>\n" " <PolyStyle>\n" " <color>800000FF</color>\n" " </PolyStyle>\n" " </Style>\n" "\n" // airport style includes runways and the // circular point-marks to which the names attach: " <Style id=$poly$>\n" " <IconStyle>\n" " <color>ffFF4040</color>\n" " <Icon>\n" " <href>http://maps.google.com/mapfiles/kml/shapes/donut.png</href>\n" " </Icon>\n" " <scale>0.5</scale>\n" " </IconStyle>\n" " <LineStyle>\n" " <color>ffFF0000</color>\n" " <width>4</width>\n" " </LineStyle>\n" " <PolyStyle>\n" " <color>40FF0000</color>\n" " </PolyStyle>\n" " </Style>\n" "\n" " <Style id=$triangle$>\n" " <IconStyle>\n" " <color>ff000000</color>\n" " <Icon>\n" " <href>http://maps.google.com/mapfiles/kml/shapes/triangle.png</href>\n" " </Icon>\n" " <scale>0.5</scale>\n" " </IconStyle>\n" " <LabelStyle>\n" " <color>ff000000</color>\n" " <colorMode>normal</colorMode>\n" " <scale>0.6</scale>\n" " </LabelStyle>\n" " </Style>\n" "\n" " <Style id=$green-poly$>\n" " <LineStyle>\n" " <color>ff00FF00</color>\n" " <width>1</width>\n" " </LineStyle>\n" " <PolyStyle>\n" " <color>8000FF00</color>\n" " </PolyStyle>\n" " </Style>\n" "\n" )); class bad_thing: public std::exception{ const char* msg; virtual const char* what() const throw() { return msg; } public: bad_thing(const char* _msg) : msg(_msg) {} }; // If indent is less than zero, don't do anything. class Bracket{ public: ostream& xout; vector<string> trailer; // Constructor Bracket(ostream& _xout) : xout(_xout), trailer(0) {} int operator() (const int indent, const string header, const string new_trailer) { trailer.push_back(string(indent, ' ') + new_trailer); xout << string(indent, ' ') << header; return indent; } // nnl is the number of newlines to add to the trailer int operator() (const int indent, const string header, int nnl = 0) { string new_trailer = header; size_t where = new_trailer.find('<'); if (where == string::npos) throw bad_thing("Bracket: missing '<'"); new_trailer.insert(1+where, "/"); new_trailer += string(nnl, '\n'); trailer.push_back(string(indent, ' ') + new_trailer); xout << string(indent, ' ') << header; return indent; } // Destructor: ~Bracket(){ for (vector<string>::const_reverse_iterator sss = trailer.rbegin(); sss != trailer.rend(); sss++) xout << *sss; } }; typedef map<string,Bucket,CaseFold> MSB; void secchi(ostream& xout, const GeoPoint center, const double phase){ // radius of secchi disk, in meters: const double secchi_rad(10.); int indent(2); Bracket gorp(xout); indent = 2 + gorp(indent, "<Polygon>\n"); xout << string(indent, ' ') << "<altitudeMode>absolute</altitudeMode>" << endl; indent = 2 + gorp(indent, "<outerBoundaryIs>\n"); indent = 2 + gorp(indent, "<LinearRing>\n"); indent = 2 + gorp(indent, "<coordinates>\n"); center.dump(xout); GeoPoint offcenter_EW(center), offcenter_NS(center); offcenter_EW.lonRad += 1.; // add one radian east offcenter_NS.latRad += 1.; // add one radian north // one radian of longitude, measured in SI meters: // roughly equal to the effective radius of curvature // of the (non-great) circle of constant latitude double radcurve_EW = center.geoDistTo(offcenter_EW); double radcurve_NS = center.geoDistTo(offcenter_NS); for (int ii = 0; ii <= 90; ii += 10) { double theta = - ii*deg + pi/4. + phase; GeoPoint circum(center); circum.lonRad += secchi_rad*sin(theta) / radcurve_EW; circum.latRad += secchi_rad*cos(theta) / radcurve_NS; circum.elev += secchi_rad; circum.dump(xout); } center.dump(xout); } class plain_handler_base { public: int placemark_isopen; string placemark_trailer; string mystyle; plain_handler_base() : placemark_isopen(0), placemark_trailer(""), mystyle("????") {} // the do_folder routine is not even virtual: void do_folder(const vector<Feature> incount, ostream& xout, const string foldername = ""); virtual void setup(){}; virtual void within_placemark(ostream& xout, const Feature&){}; virtual void do_feature(ostream& xout, const Feature&){}; virtual void end_placemark(ostream& xout){}; virtual void do_runway(ostream& xout, const Feature&, const Runway&){}; }; class kml_handler_base : public plain_handler_base{ public: virtual void within_placemark(ostream& xout, const Feature&); virtual void do_feature(ostream& xout, const Feature&){}; virtual void end_placemark(ostream& xout); virtual void do_runway(ostream& xout, const Feature&, const Runway&){}; }; class kml_handler_ascii : public plain_handler_base{ public: virtual void do_feature(ostream& xout, const Feature&); }; class kml_handler_rectangle : public kml_handler_base{ public: virtual void do_runway(ostream& xout, const Feature&, const Runway&); virtual void setup(); }; class kml_handler_secchi : public kml_handler_base{ public: virtual void do_runway(ostream& xout, const Feature&, const Runway&); virtual void setup(); }; class kml_handler_centerline : public kml_handler_base{ public: virtual void do_runway(ostream& xout, const Feature&, const Runway&); virtual void setup(); }; class kml_handler_points : public kml_handler_base{ public: virtual void do_feature(ostream& xout, const Feature&); virtual void setup(); }; void kml_handler_ascii::do_feature(ostream& xout, const Feature& feat){ int xwid(0); xout << fixed << setprecision(3) << right << setw(xwid) << feat.fwd/deg << "|" << setw(xwid) << feat.dist / output_unit << setprecision(9) << "|" << setw(xwid) << feat.loc.lonRad/deg << "|" << setw(xwid) << feat.loc.latRad/deg << "|" << setw(xwid) << left << feat.ident << setprecision(1); if (feat.loc.elev <= -9e99) xout << "|" << scientific << feat.loc.elev; // output elevation in meters; // may or may not be the smart thing to do: else xout << "|" << fixed << feat.loc.elev; xout << fixed << "|" << feat.has_tower << "|" << feat.fullname << "|" << endl; } void kml_handler_secchi::do_runway(ostream& xout, const Feature& feat, const Runway& rrr){ within_placemark(xout, feat); for (int rend = 0; rend < 2; rend++){ GeoPoint elevpoint; elevpoint = rrr.end[rend]; elevpoint.elev = feat.loc.elev; secchi(xout, elevpoint, 0.); secchi(xout, elevpoint, pi); } } void kml_handler_rectangle::do_runway(ostream& xout, const Feature& feat, const Runway& rrr){ within_placemark(xout, feat); double fwd, rev; double len_not_used = rrr.end[0].geoDistTo(rrr.end[1], fwd, rev); if (len_not_used) {} xout << " <Polygon>\n" " <altitudeMode>clampToGround</altitudeMode>\n" " <outerBoundaryIs>\n" " <LinearRing>\n" " <coordinates>\n"; GeoPoint corner = rrr.end[0].displace(fwd-pi/2, rrr.width/2.); corner.dump(xout); rrr.end[1].displace(rev+pi/2, rrr.width/2.).dump(xout); rrr.end[1].displace(rev-pi/2, rrr.width/2.).dump(xout); rrr.end[0].displace(fwd+pi/2, rrr.width/2.).dump(xout); corner.dump(xout); xout << " </coordinates>\n" " </LinearRing>\n" " </outerBoundaryIs>\n" " </Polygon>\n"; } void kml_handler_centerline::do_runway(ostream& xout, const Feature& feat, const Runway& rrr){ within_placemark(xout, feat); int indent = 6; Bracket gorp(xout); indent = 2 + gorp(indent, "<LineString>\n"); xout << string(indent, ' ') << "<altitudeMode>clampToGround</altitudeMode>\n"; indent = 2 + gorp(indent, "<coordinates>\n"); rrr.end[0].dump(xout); rrr.end[1].dump(xout); } void kml_handler_points::do_feature(ostream& xout, const Feature& feat){ within_placemark(xout, feat); xout << " <Point>" << endl; xout << " <altitudeMode>clampToGround</altitudeMode>" << endl; xout << " <coordinates>" << endl; GeoPoint tmp(feat.loc); tmp.elev = 0; tmp.dump(xout); xout << " </coordinates>" << endl; xout << " </Point>" << endl; } string std_placemark_trailer(" </MultiGeometry>\n" " </Placemark>\n"); void kml_handler_base::within_placemark(ostream& xout, const Feature& feat){ if (placemark_isopen) return; xout << "<Placemark>" << endl; xout << " <name>" << feat.ident << "</name>" << endl; xout << " <description>" << protect(feat.fullname) << "</description>" << endl; #if 0 if (need_cat == fix) xout << " <styleUrl>#triangle</styleUrl>\n"; else xout << " <styleUrl>#poly</styleUrl>\n"; #endif xout << " <styleUrl>#" << mystyle << "</styleUrl>\n"; xout << " <MultiGeometry>" << endl; placemark_isopen = 1; placemark_trailer = std_placemark_trailer; } // using green coloration for the rectangles void kml_handler_secchi::setup() { mystyle = "green-poly"; } // using triangles for the airports void kml_handler_points::setup() { mystyle = "triangle"; } void kml_handler_rectangle::setup() { mystyle = "poly"; } void kml_handler_centerline::setup() { mystyle = "poly"; // may not be correct } void kml_handler_base::end_placemark(ostream& xout){ if (!placemark_isopen) return; xout << placemark_trailer; placemark_isopen = 0; } void plain_handler_base::do_folder(const vector <Feature> grand, ostream& xout, const string foldername) { setup(); placemark_isopen = 0; if (foldername.length()) { xout << "<Folder><name>" << foldername << "</name>" << endl; xout << kml_styleblock; } for (vector<Feature>::const_iterator feat = grand.begin(); feat != grand.end(); feat++) { if (need_paved) { int is_paved(0); for (vector<Runway>::const_iterator rptr = feat->rwy.begin(); rptr != feat->rwy.end(); rptr++) { int code = rptr->sfc; if (code == 1 || code == 2) { is_paved = 1; break; } } if (!is_paved) continue; } do_feature(xout, *feat); // loop over all runways belonging to this Feature: for (vector<Runway>::const_iterator rptr = feat->rwy.begin(); rptr != feat->rwy.end(); rptr++) { do_runway(xout, *feat, *rptr); } end_placemark(xout); } // all features in featureset if (foldername.length()) { xout << "</Folder>" << endl; } } /////////////////////////////// // the big output routine // // called by collect_and_show_features // void output_featureset(const vector<Feature>& grand, ostream& xout) { if (kml_mode){ if (1) { // Areas have to be in layer #1, for GRASS // (as discussed in grass-intro.htm). kml_handler_rectangle().do_folder(grand, xout, "runways"); // Labels don't have to be in layer #1: kml_handler_points().do_folder(grand, xout, catter_name[need_cat] + "-names"); kml_handler_secchi().do_folder(grand, xout, "elevations"); } else { kml_handler_centerline().do_folder(grand, xout); } } else { kml_handler_ascii().do_folder(grand, xout); } } void collect_and_show_features(vector<Feature>& foo, vector<Feature> target_features) { ofstream xout; string fname = outfile_prefix; if (fname.length()) fname += "_"; fname += catter_name[need_cat]; if (kml_mode) fname += ".kml"; else fname += ".csv"; cerr << "Using fname " << fname << endl; xout.open(fname.c_str()); if (!xout.good()) { cerr << "Could not open output file '" << fname << "'" << endl; exit(1); } Bracket gorp(xout); if (kml_mode) gorp(0, kml_header, kml_trailer); Feature tarfeat; set<string> seen_it; vector<Feature> grand_featureset; int first_target(1); // outer loop over all targets: for (vector<Feature>::const_iterator tp = target_features.begin(); tp != target_features.end(); tp++) { tarfeat = *tp; // Preliminary inner loop: calculate distance and azimuth for each point, // and cache the results. // This is an optimization, based on the idea that calculating // the distance is expensive, and we are going to need the distance // more than once. // Indeed std::set is going to use the distance again and again // when we're not looking. for (vector<Feature>::iterator ptr = foo.begin(); ptr != foo.end(); ptr++) { double rev; ptr->dist = tarfeat.loc.geoDistTo(ptr->loc, ptr->fwd, rev); } vector<Feature> inrange; // Using std::set accomplishes two things: // It keeps the points sorted, and eliminates duplicates. set<Feature,distcomp> incount(7); // inner loop over all fixes in the world: for (vector<Feature>::const_iterator ptr = foo.begin(); ptr != foo.end(); ptr++) { string ident(ptr->ident); double dist(ptr->dist); if (need_cat != feature && ptr->cat != need_cat) continue; // range-based selector: if (dist <= max_range){ if ( (az_width >= 180.) // don't even need to compute azimuth || (dist < 1.) // azimuth is ill-determined when range is small. || fabs(diffangle(ptr->fwd, az_center)) <= az_width) { inrange.push_back(*ptr); } } // count-based selector: if (max_count > 0) { if (incount.size() < max_count || dist < incount.rbegin()->dist) { incount.insert(*ptr); } if (incount.size() > max_count) { incount.erase((++incount.rbegin()).base()); } } // box-based selector // FIXME: should be independent of target // ... but if we don't have a target, what would // we use for the distance and azimuth fields // that appear in the output? // For now, latch onto first target: if (first_target) { if ( ptr->loc.lonRad >= box1.lonRad && ptr->loc.latRad >= box1.latRad && ptr->loc.lonRad <= box2.lonRad && ptr->loc.latRad <= box2.latRad ) { inrange.push_back(*ptr); } } } // inner loop over all fixes first_target = 0; #if 1 cerr << "For target " << tarfeat.ident << " found " << inrange.size() << " plus " << incount.size() << endl; #endif // combine results from various selectors, // in such a way that they become sorted by distance: for (vector<Feature>::const_iterator xxx = inrange.begin(); xxx != inrange.end(); xxx++){ incount.insert(*xxx); } // and accumulate the results: for (set<Feature>::const_iterator xxx = incount.begin(); xxx != incount.end(); xxx++){ if ((!include_dupes) && seen_it.find(xxx->ident) != seen_it.end()) continue; seen_it.insert(xxx->ident); grand_featureset.push_back(*xxx); } } // target points output_featureset(grand_featureset, xout); } // Plot the track centerline. // (Note this does not include the corridor; // use show_corr for that.) void show_track_grass_fmt(vector<Feature> target_features) { cout << "L" << " " << fixed << setprecision(0) << target_features.size() << " " << 0 // number of categories << endl; Feature here, there; for (vector<Feature>::const_iterator tp = target_features.begin(); tp != target_features.end(); ) { here = *tp; tp++; if (tp == target_features.end()) there = here; else there = *tp; double fwd, rev, dist; dist = here.loc.geoDistTo(there.loc, fwd, rev); cout << fixed << setprecision(9) << here.loc.lonRad/deg << " " << here.loc.latRad/deg << " " << here.ident << setprecision(3) << " " << fwd/deg /* not used by grass */ << " " << dist / output_unit << " " << rev/deg << endl; } } // draw a half circle, using a total of Nseg segments, i.e. // i.e. Nseg+1 points // // Special case: if Nseg==0, just output a single point, // namely the first point that would have belonged to the // half circle. This is useful for closing contours. void endcap(ostream& xout, const Feature& here, const double facing, const double flip, const int Nseg) { double denom(Nseg); if (Nseg == 0) denom = 1; // special case for (int ii = 0; ii <= Nseg; ii++) { double theta = facing - pi/2. - flip * pi * ii / denom; // A point walking around the circle: GeoPoint walker = here.loc.displace(theta, corr_hwid); walker.dump(xout); } } class Segment_base{ public: string myname; int polygonal; void all_segments(vector<Feature> target_features); virtual void show_segment_coords(ostream& xout, const Feature here, const Feature there, int routine){}; virtual void setup(){} virtual void show_segment_kml( const int indent, ostream& xout, const Feature here, const Feature there, int routine); }; class Tracker : public Segment_base{ virtual void show_segment_coords(ostream& xout, const Feature here, const Feature there, int routine){ if (!routine) return; here.loc.dump(xout); there.loc.dump(xout); } virtual void setup() { myname = "track"; polygonal = 0; } }; class Corridor : public Segment_base{ virtual void show_segment_coords(ostream& xout, const Feature here, const Feature there, int routine); virtual void setup(){ myname = "corridor"; polygonal = 1; } }; void Corridor :: show_segment_coords(ostream& xout, const Feature here, const Feature there, const int routine){ int Nseg(18); if (!routine){ endcap(xout, here, 0, 2., 2*Nseg); } else { double dist, fwd, rev; dist = here.loc.geoDistTo(there.loc, fwd, rev); if (dist) {} // suppress compiler warning` endcap(xout, here, fwd, -1., Nseg); endcap(xout, there, rev, -1., Nseg); endcap(xout, here, fwd, -1., 0); } } void Segment_base:: show_segment_kml( const int _indent, ostream& xout, const Feature here, const Feature there, int routine){ int indent(_indent); Bracket gorp(xout); indent = 2 + gorp(indent, "<Placemark>", 1); // We are better off without a description, so we don't get balloons: //xxx xout << "<description>corridor</description>" << endl; // but we ought to provide some sort of name: xout << "<name>" << here.ident; if (routine) xout << "_" << there.ident; xout << "</name>" << endl; // Changing displayMode doesn't fix the clickable problem: //xxx xout << "<displayMode>hide</displayMode>" << endl; // xout << "<clickable>0</clickable>" << endl; xout << string(indent, ' ') << "<styleUrl>#" << myname << "</styleUrl>\n"; // not needed at the moment: //xxxx indent = 2 + gorp(indent, "<MultiGeometry>\n"); double fwd, rev, dist; dist = here.loc.geoDistTo(there.loc, fwd, rev); if (dist) {} // suppress compiler warning Bracket bork(xout); if (polygonal) indent = 2 + bork(indent, "<Polygon>\n"); else indent = 2 + bork(indent, "<LineString>\n"); if (elevation_relative) xout << string(indent, ' ') << "<altitudeMode>clampToGround</altitudeMode>\n"; else xout << string(indent, ' ') << "<altitudeMode>absolute</altitudeMode>\n"; if (polygonal) { indent = 2 + bork(indent, "<outerBoundaryIs>\n"); indent = 2 + bork(indent, "<LinearRing>\n"); } indent = 2 + bork(indent, "<coordinates>\n"); show_segment_coords(xout, here, there, routine); } // Plot the track centerline or corridor // // The first part of this code runs parallel to collect_and_show_features. void Segment_base::all_segments(vector<Feature> target_features) { setup(); ofstream xout; string fname = outfile_prefix; if (fname.length()) fname += "_"; fname += myname; fname += ".kml"; cerr << "Using fname " << fname << endl; xout.open(fname.c_str()); if (!xout.good()) { cerr << "Could not open output file '" << fname << "'" << endl; exit(1); } int indent = 0; Bracket gorp(xout); indent = 2 + gorp(indent, kml_header, kml_trailer); indent = 2 + gorp(indent, "<Folder>", 1); xout << "<name>" << myname << "</name>" << endl; xout << kml_styleblock; // special case: first target: Feature here, there; vector<Feature>::const_iterator tp = target_features.begin(); there = *tp++; // initial point there.loc.elev = track_elevation; here = there; show_segment_kml(indent, xout, here, there, 0); // loop over all routine segments ... // where a segment lies between two targets. for (; tp != target_features.end(); tp++) { here = there; // new start-point = old end-point there = *tp; there.loc.elev = track_elevation; // the segment: show_segment_kml(indent, xout, here, there, 1); // the disk between segments: show_segment_kml(indent, xout, there, there, 0); } } void suffix(const string arg, string& head, string& sfx){ int ii = arg.length(); for (; ii > 0;){ ii--; char ch(arg[ii]); if (ch == ' ') continue; if (isalpha(ch)) continue; break; } head = arg.substr(0, ii+1); sfx = arg.substr(ii+1); } int main(int _argc, const char* _argv[]) { init_catter_names(); int argc(_argc); const char** argv(_argv); string progname(*argv++); argc--; vector<string> target_names(0); for (;argc > 0;) { string arg(*argv++); argc--; if (arg.substr(0,2) == "--") arg = arg.substr(1); if (prefix(arg, "-help")) { usage(); exit(0); } else if (prefix(arg, "-azimuth")) { if (!argc) { cerr << "Option -azimuth requires two arguments" << endl; return 1; } az_center = atof(*argv++); argc--; if (!argc) { cerr << "Option -azimuth requires two arguments." << endl; return 1; } az_width = atof(*argv++); argc--; } else if (prefix(arg, "-range")) { if (!argc) { cerr << "Option -range requires an argument" << endl; return 1; } max_range = atof(*argv++) * nm; argc--; } else if (prefix(arg, "-count")) { if (!argc) { cerr << "Option -count requires an argument" << endl; return 1; } max_count = atoi(*argv++); argc--; } else if (prefix(arg, "-box")) { if (!argc) { cerr << "Option -box requires four arguments" << endl; return 1; } box1.lonRad = atof(*argv++)*deg; argc--; if (!argc) { cerr << "Option -box requires four arguments" << endl; return 1; } box1.latRad = atof(*argv++)*deg; argc--; if (!argc) { cerr << "Option -box requires four arguments" << endl; return 1; } box2.lonRad = atof(*argv++)*deg; argc--; if (!argc) { cerr << "Option -box requires four arguments" << endl; return 1; } box2.latRad = atof(*argv++)*deg; argc--; #if 0 dumpPoint(&cerr, box1); dumpPoint(&cerr, box2); double fwd, rev; double dist = box1.geoDistTo(box2, fwd, rev); cerr << "Diameter of box: " << dist/nm << " nm" << " on heading " << fwd/deg << endl; #endif } else if (prefix(arg, "-corridor")) { if (!argc) { cerr << "Option -corridor requires an argument" << endl; return 1; } corr_hwid = atof(*argv++) * nm; argc--; } else if (prefix(arg, "-elevation")) { if (!argc) { cerr << "Option -elevation requires an argument" << endl; return 1; } string head, sfx; suffix(*argv++, head, sfx); argc--; track_elevation = atof(head.c_str()); if (sfx == "ft") track_elevation *= foot; else if (sfx == "m") {} else { cerr << "-elevation requires units: ft or m" << endl; exit(1); } elevation_relative = 0; // elevation is now absolute } else if (prefix(arg, "-near")) { if (!argc) { cerr << "Option -near requires an argument" << endl; return 1; } vicinity_name = *argv++; argc--; } else if (prefix(arg, "-prefix")) { if (!argc) { cerr << "Option -prefix requires an argument" << endl; return 1; } outfile_prefix = *argv++; argc--; } else if (prefix(arg, "-meters")) { output_unit = 1.0; } else if (prefix(arg, "-apt") || prefix(arg, "-airport")) { need_cat = apt; } else if (prefix(arg, "-fix")) { need_cat = fix; } else if (prefix(arg, "-obstruction")) { need_cat = obst; } else if (prefix(arg, "-include")) { include_dupes++; } else if (prefix(arg, "-track")) { track_mode++; } else if (prefix(arg, "-kml")) { kml_mode++; } else if (prefix(arg, "-paved")) { need_paved++; } else if (arg[0] == '-') { cerr << "Unrecognized option: '" << arg << "'" << endl; return 1; } else { target_names.push_back(arg); } } // FIXME : in box mode, only reason to require at least one target // is not at all a good reason. // FWIW, in non-box mode, no targets would result in no output. if (target_names.size() == 0) { cerr << "Need at least one target; try\n"; cerr << " getafix -help\n"; exit(1); } // there were 140,000 known fixes last time I looked vector<Feature> foo; foo.reserve(250000); scan_fixes("fix.dat", 0, foo); scan_fixes("fix2.dat", 1, foo); scan_fixes("obst.dat", 1, foo, obst); scan_apts("apt.dat", 0, foo); scan_apts("apt2.dat", 1, foo); ////////////////////////////////////////////////////////////////////// // all the data has been read in. // Build the database, to facilitate looking things up by name: MSB dbase; for (vector<Feature>::const_iterator ptr = foo.begin(); ptr != foo.end(); ptr++) { dbase[ptr->ident].push_back(*ptr); } Feature vicinity; if (vicinity_name.length()) { Bucket bbb(dbase[vicinity_name]); if (bbb.size() == 0) { cerr << "Vicinity fix '" << vicinity_name << "' not found." << endl; // continue, with bated breath } else if (bbb.size() > 1) { cerr << "Vicinity fix '" << vicinity_name << "' is " << bbb.size() << "-fold ambiguous" << endl; // also continue, with bated breath } else { vicinity = bbb[0]; } } vector<Feature> target_features; for (vector<string>::const_iterator nnn = target_names.begin(); nnn != target_names.end(); nnn++){ string name = *nnn; Feature tarfeat = dbase[name].nearest_to(vicinity); if (tarfeat.cat == phantom) { cerr << "Fix '" << name << "' not found." << endl; continue; } if (tarfeat.cat == ambiguous) { cerr << "Fix '" << name << "' is " << tarfeat.has_tower << "-fold ambiguous" << endl; continue; } vicinity = tarfeat; target_features.push_back(tarfeat); } if (track_mode){ Tracker().all_segments(target_features); Corridor().all_segments(target_features); } else { // non-track mode ... find stuff in box and/or near targets // and then send it to the output files: collect_and_show_features(foo, target_features); } } // end of main |
After the script has created all the map layers, we still need to tell the system which ones we want to display. I haven’t figured out how to automate this yet.
Go to the“GIS Manager” window and click the “Add raster layer button” (for raster data, such as was input via r.in.gdal) or the “Add vector layer” button (for vector data, such as was input via v.in.ascii).
Then tell it what map you want to use. The various options are not very self-explanatory but eventually you can figure them out. Beware that some of the more advanced options don’t work.
Whenever you change an option, if you want to see the effect you need to go to the “Map Display” window and push the redraw button, the second button from the left.
Important: If you have an opaque layer, such as any sort of elevation map or shaded relief map, make sure any vectors you draw are layered on top of the opaque map; otherwise they won’t be visible and you won’t know what happened to them. Specifically, in the “GIS Manager” window, the order in which the maps are listed is important. The ones higher in the list get drawn on top of the ones lower in the list.
Choosing the font to be used for labels on the graph apparently needs to be done by hand, every time you start the GUI. Evidently it is not part of the configuration stored in the .grc file.
Here are only a small fraction of the bugs I’ve noticed:
You can restart the GUI: It restarts quickly, so this is a good way to work around GUI bugs: : +++; gis.m dmrc=LCC.grc 1a) d.where seems to be grossly broken. Runs amok. 1b) Calling v.what or d.what.vect from the "run" window generates no events. 2) v.what seems to sorta work *if* called by the button on the gui, but then only applies to one map at a time. I wanted to be able to query the map, to identify features et cetera. I've found the following dirty workaround: On the ``Map Display'' window, click the ``NVIZ flythrough path'' button, and then push the ``mouse'' button, then click on the map. Go back to the ``NVIZ flythrough path'' popup and copy the numbers from the window next to the ``mouse'' button. This is useful because I can't get any of the mouse-related ``d.'' modules to work. List available database files (aka map layers and mapsets) in the format "type/layer@MAPSET" : +++; g.mlist -t -m type=all less convenient format: : ???; g.list type=rast | cat find name of current location : +++; g.gisenv get=LOCATION_NAME dump *all* gis env variables: : +++; g.gisenv --q Find out about current "region" i.e. clipping region. If this is not right lots of things will fail insidiously! : +++; g.region -p Find out about current projection: : +++; g.proj -p Find out about projection used by file: : +++; g.proj -p -d georef=N32W111.tif Read in a file ... and create location accordingly ... assuming you have put bounding-box and projection info into the file: : +++; r.in.gdal input=N32W111.tif output=m111 --overwrite location=z111 Read it in ... without messing with the location: : +++; r.in.gdal input=N32W111.tif output=m111 --overwrite Note that if location already exists, you can switch to it using g.mapset. To find out what columns a map layer has : +++; v.info -c map=$foo layer=$bar There does not appear to be a corresponding "r.info -c" for raster maps. Dump all the "attributes" of a vector map layer, i.e. all the rows and columns of data including e.g. the names of the data points (but excluding the latitude and longitude, which are somehow not considered "attributes") : +++; db.select table=$foo Again, there does not appear to be any corresponding way of looking at raster files. If the map "$foo" has layers, then the table names will have suffixes: : +++; db.select table=${foo}_1 : +++; db.select table=${foo}_2 # et cetera When reading .kml files into grass, kml "folders" produce grass "layers". Grass apparently won't plot an area unless the boundary is /closed/. So at the end, repeat the first point. In contrast, almost every other graphics program in the world will plot the interior of a rectangle given three of the four sides. Grass apparently won't plot the labels of areas, just points. So if you want to label areas, you need to provide points (as well as areas) in your data file. This is tricky, because the gis.m GUI won't let you control the fill color of areas for any layer other than layer #1. So put the areas in layer #1, and put the labels in layer #2. (The GUI gives you a choice as to which layer the labels come from, but no corresponding choice for the areas.) This gets even trickier if you have multiple areas that you might want to fill (runways, route corridor, et cetera). For google-earth it is convenient to put them in multiple folders in the same document, but for grass AFAICT you are stuck putting them in separate files. You can display an image on the screen without fussing with the gis.m GUI: : +++; d.mon stop=x0 # kill old x0, if any, so it forgets the region : +++; g.region rast=$tile -p # set region BEFORE starting x0 : +++; d.mon start=x0 : +++; d.rast $tile Similarly, you can create an image on disk without fussing with the gis.m GUI: : +++; d.mon start=PNG : +++; d.vect map=$mapname@PERMANENT color=0:0:0 lcolor=255:0:0 \ fcolor=255:0:0 display=shape,attr type=point,line,boundary,area \ icon=basic/circle size=30 layer=2 attrcol=Descriptio \ lsize=15 xref=left yref=center llayer=2 : +++; d.mon stop=PNG : +++; display map.png Grass stores "locations" in subdirectories in the grassdata directory ... and indeed it thinks every subdirectory is a location, which gets ugly in some cases, e.g. if you have a set of ESRI data that takes the form of a directory full of files. To get a list of /possible/ names of locations that have already been defined, try (in the grassdata directory) : +++; ls -d */PERMANENT Switch to a different location: : +++; g.mapset mapset=PERMANENT location=z111 Switch back (if ever needed): : +++; g.mapset mapset=PERMANENT location=UTM_4326_SRTM find out basic stuff about some map layer: : +++; r.info m111 # (for raster map) : +++; v.info apts # (for vector map) r.in.gdal allows a user to create a (binary) GRASS raster map layer, or imagery group, from any GDAL supported raster file format, with an optional title. The imported file may also be optionally used to create a new location. manpage: file:///usr/lib/grass64/docs/html/r.in.gdal.html formats: http://www.gdal.org/formats_list.html #Spatial Reference Set #for local Lambert Conformal Conic projection #covering the whole bounding box (i.e. all the tiles) : +++; /usr/local/gdal/bin/gdalwarp -of VRT -s_srs EPSG:4326 -t_srs \ '+proj=lcc +lat_1=33n +lat_2=45n +lon_0=97w' mcd12_sep2109.asc \ lambert_mcd12_sep2109.vrt #Reference: #http://www.mail-archive.com/gdal-dev@lists.osgeo.org/msg06041.html
These are largely notes made before writing the script. Many of the ideas noted here were incorporated into the script. Some of them have since been changed. In particular, a bevy of small but annoying changes changes were necessary in order to convert from Mercator to Lambert projection.
## Some useful grass commands: TILE=N37E141 # mark Fukushima #1 and #2 echo -e '141.015|37.233|"F1"\n141.025556|37.316389|"F2"' | \ v.in.ascii output=marks --overwrite # Some typical data fetch commands: TILE=N32W112 wget -c http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America/$TILE.hgt.zip unzip $TILE.hgt.zip TILE=N37E141 wget -c http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/Eurasia/$TILE.hgt.zip unzip $TILE.hgt.zip # read in the data directly, in UTM projection, # without bothering to reproject it using gdal: r.in.srtm $TILE.hgt --overwrite r.info map=$TILE # One way to set the region; # not so good if you have multiple tiles: g.region rast=$TILE -p # ## works: g.region rast=$TILE -p ## fails: d.rast map=$TILE catlist=915-9999 ## manpage: file:///usr/lib/grass64/docs/html/d.mon.html ## Kludge: if the map window is cockeyed, ## set the region the way you want it, ## then delete the map window; ## when it gets recreated it will be focused on the right region # create shaded relief map: r.shaded.relief map=$TILE --overwrite r.colors map=$TILE.shade color=gray # probably the default # Note: color=rainbow is (for now) favored for the elevation data # ... as opposed to the shaded relief. # haxby and haxby -e are both OK # don't use red or magenta # elevation is OK, kinda orange, but so are the FAA sectionals # doesn't use red or magenta # terrain is OK, kinda green, somewhat subdued impression # doesn't use red or magenta # etopo2 == same as terrain # byg = unusual, could be useful, takes some getting used to # doesn't use red or magenta # corine = boring, mostly white # srtm = boring, brown # ramp = too stark # bcyr = OK # rainbow -e and bcyr -e have much more yellow and red # Get data from the map, assuming it uses lat/lon # i.e. not Lambert: r.what input=$TILE east_north=-110.791,32.44 # Mount Lemmon ## result: ## -110.791|32.44||2778 ## read a KML file: g.mapset mapset=PERMANENT location=UTM_4326_SRTM v.in.ogr -z --overwrite dsn=foo.kml output=demo g.mapset mapset=PERMANENT location=z111 # get a list of maps that can be pulled over: v.proj location=UTM_4326_SRTM -l v.proj location=UTM_4326_SRTM input=demo --overwrite ## Note that r.what can read from standard input ## and claims that the standard field-separator is '|', ## but in fact it insists on fs=space, and it cannot be changed. ## cat apt.csv | v.in.ascii x=3 y=4 fs=, output=apts --overwrite ## v.info map=apts ## g.region rast=$TILE1,$TILE2 -p ## cat track.asc | v.in.ascii -n out=track format=standard --overwrite ## cat fix.csv | v.in.ascii fs=, output=fixes x=3 y=4 \ ## columns="range double precision, az double precision, x double precision, y double precision, ident varchar(20)" ## v.info -c map=fixes ## g.remove rast=some_old_thing ## TILE=N32W111 ## g.region rast=$TILE -p ## r.contour --overwrite input=$TILE output=${TILE}_con3 levels=914,2743 ## r.contour --overwrite input=$TILE output=${TILE}_con6 levels=1829 ##** You can waste huuuuge amounts of time if the region isn't set right: ##** g.region rast=$TILE -p ##** g.region rast=$TILE1,$TILE2 -p ## Other options, not necessarily recommended: ## wget ??? http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/$TILE.hgt.zip ## r.colors $TILE.shade rast=shaded112 ## r.shaded.relief map=$TILE shadedmap=$TILE.shade --overwrite ## d.everything fails: allegedly no monitor: ##// d.rast map=$TILE catlist=915-9999 ##// d.font font="DejaVuSans-Bold" ## Works from the type-in window of the "Output - GIS.m" popup. ## d.font path=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf ## Documentation ## file:///usr/lib/grass64/docs/html/d.mon.html ## converting from SRTM (which is UTM 4326) to Lambert: ## http://www.osgeo.org/pipermail/gdal-dev/2010-March/023793.html
// First MUST set up datum and ellipsoid; see // http://grass.osgeo.org/newsletter/GRASSNews_vol3.pdf // // If this is a first-time startup in an empty directory, // push the EPSG button with code 4326 // http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America/N32W111.hgt.zip // Bigger, not necessarily better: // http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N32W112.hgt.zip TILE=N37E141 # Fukushima #1 echo -e '141.015|37.233|"F1"\n141.025556|37.316389|"F2"' | v.in.ascii output=marks --overwrite TILE=N32W112 wget -c http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/Eurasia/$TILE.hgt.zip wget -c http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/North_America/$TILE.hgt.zip unzip $TILE.hgt.zip r.in.srtm $TILE.hgt --overwrite ## Color table for raster map <N32W111> set to 'srtm' r.info map=$TILE g.region rast=$TILE -p // --> delete the map window if it is cockeyed; // when it gets recreated it will be focused on the right region r.shaded.relief map=$TILE --overwrite r.colors map=$TILE.shade color=gray g.region rast=$TILE -p // fails: d.rast map=$TILE catlist=915-9999 // file:///usr/lib/grass64/docs/html/d.mon.html // ... r.what input=$TILE east_north=-110.791,32.44 # Mount Lemmon ## -110.791|32.44||2778 cat apt.csv | v.in.ascii x=3 y=4 fs=, output=apts --overwrite v.info map=apts g.region rast=$TILE1,$TILE2 -p cat track.asc | v.in.ascii -n out=track format=standard --overwrite cat fix.csv | v.in.ascii fs=, output=fixes x=3 y=4 columns="range double precision, az double precision, x double precision, y double precision, ident varchar(20)" --overwrite v.info -c map=fixes g.remove rast=some_old_thing TILE=N32W111 g.region rast=$TILE -p r.contour --overwrite input=$TILE output=${TILE}_con3 levels=914,2743 r.contour --overwrite input=$TILE output=${TILE}_con6 levels=1829 //** You can waste huuuuge amounts of time if the region isn't set right: g.region rast=$TILE -p g.region rast=$TILE1,$TILE2 -p // Other options, not necessarily recommended: ??? wget http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/$TILE.hgt.zip // r.colors $TILE.shade rast=shaded112 // r.shaded.relief map=$TILE shadedmap=$TILE.shade --overwrite // d.everything fails from GRASS shell window: allegedly no monitor: ??? d.rast map=$TILE catlist=915-9999 ??? d.font font="DejaVuSans-Bold" // Works from the type-in window of the "Output - GIS.m" popup. d.font path=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf // Documentation // file:///usr/lib/grass64/docs/html/d.mon.html // converting from SRTM (which is UTM 4326) to Lambert: // http://www.osgeo.org/pipermail/gdal-dev/2010-March/023793.html