summaryrefslogtreecommitdiff
path: root/ipl/gprogs/dlgvu.icn
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/gprogs/dlgvu.icn')
-rw-r--r--ipl/gprogs/dlgvu.icn1900
1 files changed, 1900 insertions, 0 deletions
diff --git a/ipl/gprogs/dlgvu.icn b/ipl/gprogs/dlgvu.icn
new file mode 100644
index 0000000..63a933d
--- /dev/null
+++ b/ipl/gprogs/dlgvu.icn
@@ -0,0 +1,1900 @@
+############################################################################
+#
+# File: dlgvu.icn
+#
+# Subject: Program to display USGS DLG map files
+#
+# Authors: Gregg M. Townsend and William S. Evans
+#
+# Date: October 2, 2005
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Contributor: Frank Glandorf
+#
+############################################################################
+#
+# Dlgvu displays and prints USGS digital map data.
+#
+# usage: dlgvu [options] file...
+#
+# Each file argument is one of:
+# a directory containing DLG files in SDTS format
+# a ZIP format archive of such files (requires "unzip" utility)
+# a text file containing coordinates of paths and features
+#
+# All interaction is via mouse actions and keyboard shortcuts.
+# The display window may be resized as desired.
+#
+############################################################################
+#
+# Command options:
+#
+# -c display coverage map only, without loading data
+# -d print some debugging data
+# -n no display; just report statistics, and then quit
+# (this still requires X-Windows, unfortunately)
+# -p use slower, more precise coordinate conversion
+# -q quiet mode: no commentary to stdout
+# -t load only maps traversed by paths, ignoring others
+# -o logfile specify output log to use instead of standard output
+#
+# -l abcd display only layers a, b, c, d
+# -x abcd exclude layers a, b, c, d
+#
+# For -l and -x, the following layer codes are used.
+# (USGS equivalents are given in parentheses.)
+#
+# b boundaries (BD: boundaries)
+# c contour lines (HP: hypsography)
+# d sand, gravel, lava (NV: nonvegetative features)
+# f feature labels read from text files
+# g GPS paths read from text files
+# l land sections (PL: public lands)
+# m markers (SM: survey markers)
+# n file names
+# o other unknown layers from non-DLG data
+# r roads (RD: roads)
+# s structures (MS: manmade structures)
+# t train tracks (RR: railroads)
+# u utilities (MT: miscellaneous transportation)
+# v vegetation (SC: surface cover)
+# w water (HY: hydrology)
+#
+# Additionally, the standard Window() options are accepted;
+# in particular, "-F fgcolor" sets the color used for drawing
+# unrecognized ("other") layers, as from USGS "National Atlas"
+# digital files, and "-G 800x500" sets the initial window size.
+#
+# Typical usage is simply
+# dlgvu dir1 [dir2 ...]
+# to display one or more adjacent maps. The -x option can speed
+# things up by excluding unwanted layers; the contour layer is
+# especially slow.
+#
+# A ZIP archive can replace a directory name if Icon can open
+# the unzip program via a pipe. For example:
+# dlgvu woodside.zip palo_alto.zip
+#
+############################################################################
+#
+# Mouse actions:
+#
+# To zoom to a particular region, sweep out the region using the
+# left mouse button. To cancel a sweep, reduce its width or height
+# to fewer than ten pixels.
+#
+# If nothing appears to be happening after zooming in, the program
+# is probably drawing offscreen. It's not smart about that. Be
+# patient, and it will soon display the visible region.
+#
+# To display the latitude / longitude of a location, and a scale bar,
+# hold down the right mouse button.
+#
+# To record a labeled feature, shift-click the left mouse button.
+# Enter a name in the pop-up dialog box. The location and name are
+# written to the log file and added to the feature layer of the map.
+#
+# To record an anonymous location to the log file, shift-click with
+# the right mouse button instead. No dialog box appears. A sequence
+# of anonymous locations can be read as a path by a subsequent
+# program run.
+#
+############################################################################
+#
+# Keyboard actions:
+#
+# + or = zoom in
+# - or _ zoom out
+# 0 or Home zoom to initial view
+# arrow keys pan the display (hold Shift key for smaller pan)
+#
+# b, c, d, etc. toggle display of specified layer
+# a display all loaded layers including n (file names)
+# x display no layers (just an empty window)
+#
+# Esc stop drawing (any unrecognized key does this)
+# space or Enter redraw screen (e.g. after inadvertent interrupt)
+# q quit
+#
+# p or PrntScrn print visible portion to PostScript file
+#
+# The file produced by PrntScrn is an Encapsulated PostScript file
+# suitable either for direct printing ("lpr file.ps") or for import
+# into another document.
+#
+############################################################################
+#
+# Input files:
+#
+# In directories and archives, only files with names ending in .ddf
+# or .DDF are read; others are ignored. These files must be in SDTS
+# (Spatial Data Transfer Standard) format, which is used by the USGS
+# for all new DLG files.
+#
+# Text files supply coordinates for features or paths. GPS receivers
+# are one possible source for such data. A text file can supply
+# paths, features, or both.
+#
+# Paths are specified by sequences of lines that end with two decimal
+# values. The values are interpreted as latitude and longitude, in
+# that order. An interruption in the sequence (such as a blank line)
+# indicates a break between paths.
+#
+# Features, or waypoints, are given by lines that *begin* with two
+# decimal values. The rest of the line is taken as a label, which
+# must not be empty and must not end with two decimal values.
+#
+# Any other line in a text file breaks a path sequence but is
+# otherwise ignored.
+#
+############################################################################
+#
+# About DLG files:
+#
+# Dlgvu was written to display digitized topographic maps produced
+# by the United States Geological Survey (USGS). The current file
+# format is based on the Spatial Data Transfer Standard (SDTS).
+# Some older files are available in other formats (including
+# "standard" and "optional") not supported by this program.
+#
+# DLG files are available free from the USGS at this web page:
+# http://edc.usgs.gov/doc/edchome/ndcdb/ndcdb.html
+# Coverage is incomplete. 24K maps, the most detailed, are available
+# for only some areas, and many maps lack some of the data layers.
+#
+# Each map is represented by a collection of gzipped tar files
+# (one for each map layer) that are unpacked for display. Multiple
+# files versions may be available, and not all layers are available
+# for all maps.
+#
+# IMPORTANT: Do not blindly unpack all the tar files of a map into
+# the same directory; due to the use of duplicate file names in the
+# transportation layers, some files will be overwritten. Instead,
+# unpack the roads, railroads, and miscellaneous transportation
+# layers separately, each time renaming the TR*.DDF files to RD*.DDF,
+# RR*.DDF, and MT*.DDF respectively.
+#
+# Dlgvu has mainly been tested and tuned using "large scale" DLG
+# files (1:24000 scale, covering 7.5 minute quadrangles). Other
+# scales produce less attractive displays, partly due to different
+# encodings: For example, the same residential streets may be encoded
+# as "Class 3 Roads" in 24K files but "Class 4 Roads" in 100K files.
+#
+# Dlgvu does not presume to understand ISO 8211, DDF, STDS, and TVP
+# in their full complexity and generality. Undoubtedly it is making
+# some unwarranted assumptions based on observed practice. The file
+# renaming recommended above is contrary to specification but allows
+# a complete map to be stored in a single flat directory.
+#
+# For more information, and some sample data files, visit:
+# http://www.cs.arizona.edu/icon/oddsends/dlgvu/
+#
+############################################################################
+#
+# Displayed features:
+#
+# DLG files are rich in detail. Dlgvu displays only some of this
+# encoded data.
+#
+# Put simply, dlgvu understands point and line features but not
+# area features. It draws a small square for a structure location,
+# or draws the outline of a large building, but it does not color in
+# an "urban area" in which individual structures are not plotted.
+# It displays the shoreline of a river, and any islands, but does
+# not understand enough to color the river area itself blue.
+#
+# Dlgvu recognizes some line features for special display. For
+# example, major roads are drawn with wide lines, and trails are
+# drawn with dashed red lines. Lines with unrecognized attributes,
+# or no attributes, are drawn in a default style. Point features
+# ("school", "windmill", etc.) are not distinguished.
+#
+# Area features are drawn only in outline. The most obvious of
+# these are vegetated areas and urban areas. Land section and
+# civil boundaries also delimit area features.
+#
+# Colors are assigned as follows (with layer codes on the left):
+#
+# b boundaries gold
+# c contour lines tan
+# f feature labels black
+# g GPS path bold pink over "highlighter"
+# l land sections pale red
+# m survey markers blue
+# n file names green
+# o other data brown (can override with -F option)
+# r roads, class 1-3 black or dark gray
+# r roads, class 4-5 dashed dark gray
+# r trails dashed red
+# s structures brownish gray
+# t railroads rust
+# t rapid transit rails dark blue
+# u pipelines dashed purple
+# u power lines purple
+# u airport runways gray
+# v vegetation light green
+# w water light blue
+# x sand, gravel, lava greenish gray
+#
+# Dlgvu uses a simple rectangular projection that is satisfactory
+# for small areas like 24K quadrangles but less suitable for large
+# areas such as whole states.
+#
+############################################################################
+#
+# The loading process:
+#
+# Data is loaded in two phases. A quick preloading phase determines
+# the available layers and the geographic areas covered. A status
+# line is output for each file. For example:
+#
+# bcl-r-tu-w N27 15 C66 42a 93a ia/ames-w
+#
+# The first field shows which layers were found. N27 declares that
+# coordinates use the NAD 1927 geodetic datum; N83 for NAD 1983 is
+# the other likely value. 15 is the UTM zone number; state maps with
+# latitude/longitude data show "LL" here. C66 means that the data
+# appears to have been projected using the Clarke 1866 ellipsoid; the
+# other likely value is "G80" for the GRS 1980 ellipsoid. Dlgvu uses
+# this to infer the datum, because the declared datum value is a less
+# reliable indicator.
+#
+# "42a 93a" gives the coordinates of the southeast corner of the map,
+# in degrees North and West, with letters "a" through "h" indicating
+# fractions from 0/8 through 7/8. The final field is the file name.
+#
+# If the layers in a file are inconsistent (for example, in the
+# inferred ellipsoid), multiple lines appear with a "*" prefix.
+# If display of a file is suppressed by the "-t" option, an X
+# prefixes the line.
+#
+# For text files, a notation such as "3:489+0" replaces layer
+# indicators, counting continuous segments, total points, and
+# feature labels. Coordinate values are assumed to use the
+# WGS 1984 ("W84") datum and ellipsoid.
+#
+# The display appears during the longer second loading phase. For
+# each layer of each input file, bounds are drawn and a progress bar
+# changes as data is read. The color of the label indicates the
+# layer being loaded.
+#
+############################################################################
+#
+# Tiling multiple maps:
+#
+# Multiple maps are displayed in proper relation. To quickly see
+# how the maps of a set will join, use "dlgvu -c".
+#
+# Small gaps or overlaps occasionally appear along boundaries when
+# maps are tiled; these are symptomatic of inconsistent datums, and
+# they reflect the true relationships of the maps to the earth.
+#
+# Dlgvu loads all necessary data into memory, so there is a very
+# real limit to the amount of data that can be displayed. Contour
+# lines, especially, take lots of memory, but they can be excluded
+# by calling "dlgvu -xc". A 128MB Linux system can typically
+# display three to five complex 24K quadrangles simultaneously
+# without thrashing.
+#
+############################################################################
+#
+# Known problems:
+#
+# On Linux, we have seen occasional crashes of the XFree86 server,
+# especially under conditions of tight memory and/or extreme zooming.
+#
+# Colors on printed maps vary somewhat from those seen onscreen,
+# depending on the printer. Printed maps do not include the "n"
+# (file name) layer.
+#
+# While data is being loaded from a ZIP file, interrupting with ^Z
+# can disrupt the "unzip" pipe and cause the program to crash or to
+# display artifacts after resumption.
+#
+# Some 100K USGS maps come with multiple sets of boundary files,
+# leading to file name collisions for which no workaround has been
+# found.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: cartog, clipping, ddfread, geodat, graphics, io, mapnav,
+# numbers, options, pscript, random, strings, wildcard, zipread
+#
+############################################################################
+
+
+
+$include "keysyms.icn"
+
+link cartog
+link clipping
+link ddfread
+link geodat
+link graphics
+link io
+link mapnav
+link numbers
+link options
+link pscript
+link random
+link strings
+link wildcard
+link zipread
+
+
+
+$define DLG_LAYERS "boclvdwsrtum" # all "real" layers, in loading order
+
+$define WSIZE "size=1000,1000" # default window size
+$define ZOOMF 1.5 # zoom factor
+
+$define MAXFONT 18 # maximum font size
+$define MINFONT 8 # minimum font size
+$define MINBOLD 10 # minimum bold font size
+
+$define MEGABYTE (1024 * 1024) # how many bytes in a megabyte?
+$define STRSIZE (1 * MEGABYTE) # default string region size
+$define BLKSIZE (16 * MEGABYTE) # default block region size
+$define MAXDRAW 4000 # maximum (even) args to avoid error 301
+
+$define DEGSCALE 1.0e+6 # divisions per degree for integer scale
+
+# parameters for displaying progress during loading
+$define PINTERVAL 100 # progress interval
+$define PSQUARES 8 # number of progress squares
+$define PSQSIZE 10 # size of progress squares
+$define PSQGAP 2 # size of gap between squares
+
+# PostScript output parameters
+$define PSSCALE 10 # scaling from pixels to PS units
+$define PSPT 24 # size of point feature in PS units
+$define PSLWI 120 # linewidth scaling factor
+
+
+
+record arg ( # command-line argument, excluding options
+ name, # file or directory name
+ type, # "dir", "zip", or "txt"
+ wanted, # if a wanted layer (null if suppressed by -t option)
+ ltable, # table of layer records, indexed by layer code
+ pcount # progress bar counter
+ )
+
+record layer ( # one layer in one directory (or zip file)
+ lcode, # layer code character
+ arg, # corresponding arg record
+ files, # list of file names
+ zone, # UTM zone, or -1 if data is in lat/lon , from XREF file
+ xscale, yscale, # scaling factors for file values
+ datum, # stated coordinate datum, from XREF file
+ ellipsoid, # inferred geodetic ellipsoid
+ icorners, # map corners in input terms
+ ocorners, # map corners as projected to lat,lon
+ px, py, # progress reporting coordinates
+ wd # width of layer in screen units
+ )
+
+record attrec ( # line drawing attributes:
+ seq, # drawing sequence
+ lcode, # layer code
+ key, # table key (layer or attribute code)
+ width, # line width
+ color, # line color
+ style, # line style
+ segs # list of segments (list of paths)
+ )
+
+record feature ( # feature or waypoint
+ lat, # latitude
+ lon, # longitude
+ label # label
+ )
+
+
+
+global arglist # list of arg records
+global opts # command options
+global chosen # cset of chosen layers
+
+global xmin, xmax, ymin, ymax # data range
+global aspect # input coordinate aspect ratio
+
+global attrib # attribute style table
+global slist # list of style records w/ seg lists
+global pcolors # list of path background colors
+
+global features # list of feature records
+
+global logfile # feature log file, if any
+
+
+
+# main program
+
+procedure main(args)
+ local a, c, e, g, i, r, s, t, v
+
+ # use large region sizes for better efficiency
+ collect(2, STRSIZE) # string region
+ collect(3, BLKSIZE) # block (heap) region
+
+ # open window first, to validate and remove any window options
+ Window("label=--", "gamma=1.5", "bg=white", "fg=brown",
+ "resize=on", "canvas=hidden", WSIZE, args)
+
+ randomize()
+ initattrib()
+
+ # process command options
+ opts := options(args, "o:l:x:cdnpqt")
+ if \opts["o"] then {
+ if opts["o"] == "-" then
+ logfile := &output
+ else
+ logfile := open(opts["o"], "w") | stop("cannot write ", opts["o"])
+ }
+ else
+ logfile := &output
+
+ chosen := cset(\opts["l"]) | (&lcase -- 'n') # start with explicit layers
+ chosen ++:= 'go' # add paths & other data, if loaded
+ chosen --:= cset(\opts["x"]) # now apply exclusions
+
+ # any remaining arguments are directory names
+ if *args = 0 then
+ stop("usage: ", &progname, " [options] dir...")
+
+ # build list of arg records, classifying each filename or directory
+ arglist := []
+ every s := !args do {
+ if directory(s) then
+ t := "dir"
+ else if iszip(s) then
+ t := "zip"
+ else
+ t := "txt"
+ put(arglist, arg(s, t, 1))
+ }
+
+ # scan text files first, because we haven't really done any validation
+ # (any unrecognized file is classified as a text file)
+ features := []
+ every (a := !arglist) & (a.type == "txt") do
+ rdtext(a)
+
+ # take inventory of DLG directories and files, and load XREF/NPnn info
+ every (a := !arglist) & (a.type ~== "txt") do {
+ inventory(a)
+ every r := !a.ltable do {
+ loadref(r)
+ if r.zone >= 0 then
+ loadcorners(r)
+ else
+ loadbounds(r)
+ }
+ if \opts["t"] & not traversed(!a.ltable) then
+ a.wanted := &null
+ lstats(a)
+ }
+
+ if \opts["n"] then
+ return
+ (*(!arglist).ltable > 0) | stop("no data")
+
+ # show initial screen
+ winit()
+ mapinit(draw, , xmin, xmax, ymax, ymin, aspect)
+ if WAttrib("label") == "--" then # set window label, if not specified
+ WAttrib("label=" || args[1])
+ WAttrib("canvas=normal") # make window visible
+ Font("sans,bold,72")
+ Fg("pale yellowish gray")
+ DrawString(60, 120, "LOADING...")
+
+ if \opts["c"] then # if just coverage wanted
+ chosen := 'n' # turn on names, turn off loaded paths
+ else {
+
+ # finally: load in the data
+ alllabels() # show coverage while loading
+ every c := !DLG_LAYERS do # load by layers
+ every a := !arglist do
+ if \a.wanted then
+ loadlayer(\a.ltable[c])
+
+ # report memory usage
+ every put(c := [], &collections)
+ collect()
+ every put(a := [], &storage)
+ if /opts["q"] then {
+ write(" ", (a[2] + a[3] + MEGABYTE / 2) / MEGABYTE,
+ " MB loaded (", c[3], "+", c[4], " GC)")
+ }
+ }
+
+ # put segment lists in order for drawing
+ # shuffle segments of each list to minimize "dead time" drawing offscreen
+ every put(slist := [], !attrib)
+ slist := sortf(slist, field(attrec, "seq"))
+ every g := (!slist).segs do
+ every !g :=: ?g # imperfect but good enough shuffle
+
+ # report attribute counts, if -d given
+ if \opts["d"] then {
+ write()
+ every e := !slist do
+ if *e.segs > 0 then
+ write(right(e.seq, 3), ". ", e.lcode, " ",
+ left(e.key, 8), right(*e.segs, 7))
+ write()
+ }
+
+ # consume any events that may have occurred during loading
+ while *Pending() > 0 do
+ Event()
+
+ # draw initial screen
+ EraseArea()
+ mapgen()
+
+ # process interactive commands
+ repeat case e := Event() of {
+ &shift & &lpress: { logfeat(e) }
+ &shift & &rpress: { logfeat(e) }
+ &rpress: { locate() }
+ !"\n\r ": { mapgen() }
+ !"pP" | Key_PrSc: { print(); Bg("white") }
+ !"aA": { chosen := &lcase; mapgen() }
+ !"xX": { chosen := ''; EraseArea(); mapgen() }
+ !"qQ": { exit() }
+ any(&letters, e) & e: {
+ e := map(e)
+ if any(chosen, e) then {
+ chosen --:= e
+ EraseArea()
+ mapgen()
+ }
+ else {
+ chosen ++:= e
+ mapgen()
+ }
+ }
+ default: { mapevent(e) }
+ }
+end
+
+
+
+# rdtext(arg) -- read a text file of paths and features
+
+procedure rdtext(arg)
+ local f, i, n, r, s, t, w, line
+ local lat, lon, alt, segs, points, nsegs, npts, nfeat
+ local xmn, xmx, ymn, ymx
+ static npaths
+ initial npaths := 0
+
+ f := open(arg.name) | stop("cannot open: ", arg.name)
+ s := "g" || (npaths % *pcolors + 1)
+ npaths +:= 1
+
+ segs := attrib[s].segs
+ nsegs := *segs
+ npts := 0
+ nfeat := 0
+ xmn := ymn := +180 * DEGSCALE
+ xmx := ymx := -180 * DEGSCALE
+
+ points := []
+ while line := read(f) do { # read line
+ every put(w := [], words(line)) # break into fields
+ # check first for path entry
+ if (lat:=real(w[-3])) & (lon:=real(w[-2])) & (alt:=real(w[-1])) &
+ (-90.<=lat<=90.) & (-180.<=lon<=180.) & (-1400<alt<30000) then {
+ npts +:= 1
+ lon *:= DEGSCALE
+ lat *:= DEGSCALE
+ put(points, integer(lon), integer(lat))
+ xmn >:= lon
+ ymn >:= lat
+ xmx <:= lon
+ ymx <:= lat
+ }
+ else if (lat := real(w[-2])) & (lon := real(w[-1])) &
+ (-90. <= lat <= 90.) & (-180. <= lon <= 180.) then {
+ npts +:= 1
+ lon *:= DEGSCALE
+ lat *:= DEGSCALE
+ put(points, integer(lon), integer(lat))
+ xmn >:= lon
+ ymn >:= lat
+ xmx <:= lon
+ ymx <:= lat
+ }
+ else {
+ # interrupt path sequence
+ if *points > 0 then {
+ put(segs, points)
+ points := []
+ }
+ # check for feature (waypoint) label
+ if (lat := real(get(w))) & (lon := real(get(w))) &
+ (-90. <= lat <= 90.) & (-180. <= lon <= 180.) then {
+ nfeat +:= 1
+ lon *:= DEGSCALE
+ lat *:= DEGSCALE
+ xmn >:= lon
+ ymn >:= lat
+ xmx <:= lon
+ ymx <:= lat
+ s := ""
+ while s ||:= " " || get(w)
+ put(features, feature(lat, lon, s[2:0]))
+ }
+ }
+ }
+ if *points > 0 then
+ put(segs, points)
+
+ nsegs := *segs - nsegs
+ if nsegs = 0 & nfeat = 0 then
+ stop("no data: ", arg.name)
+
+ r := layer("g", arg)
+ r.zone := -1
+ r.datum := "WGS84"
+ r.ellipsoid := "WGS84"
+ r.icorners := r.ocorners := [xmn, ymn, xmn, ymx, xmx, ymx, xmx, ymn]
+ t := table()
+ t["g"] := r
+ arg.ltable := t
+
+ n := 0
+ every n +:= *segs[-nsegs to -1]
+ if /opts["q"] then
+ write(right(nsegs || ":" || npts || "+" || nfeat, 14), " ", lsumm(r))
+
+ close(f)
+ return
+end
+
+
+
+# ddpopen(r, p) -- generate open DDF files from layer r matching pattern p
+
+procedure ddpopen(r, p)
+ local a, f, d, s, fname
+
+ a := r.arg
+ every fname := !r.files do {
+ if not (map(fname) ? wild_match(p)) then
+ next
+ s := a.name || "/" || fname
+ f := &null
+ if a.type == "zip" then
+ f := zipfile(a.name, fname)
+ else
+ f := open(s, "ru")
+ d := ddfopen(\f) | stop("cannot open as DDF: ", s)
+ suspend d
+ }
+ fail
+end
+
+
+
+# inventory(a) -- inventory arg entry a
+
+procedure inventory(a)
+ local b, c, f, fname, m, flist, trcount
+
+ # load filenames into list, because we need to scan it twice
+ flist := []
+ if a.type == "zip" then
+ f := zipdir(a.name)
+ else
+ f := open(a.name)
+ while put(flist, read(f))
+ close(f)
+
+ # count TR01LE??.DDF files
+ trcount := 0
+ every fname := !flist do
+ if map(fname) ? (tab(-12) & ="tr01le") then
+ trcount +:= 1
+
+ # classify files and save the ones we want
+ a.ltable := table()
+ every fname := !flist do {
+ map(fname) ? {
+ while tab(upto('/') + 1)
+ pos(-12) | next
+ move(8) | next
+ =".ddf" | next
+ }
+ b := fname[-12:-4] | next
+
+ every c := !lcodes(b, trcount) do {
+ if any(chosen, c) then {
+ # this is a wanted file in a wanted layer; remember it
+ /a.ltable[c] := layer(c, a, [])
+ put(a.ltable[c].files, fname)
+ }
+ }
+ }
+
+ return
+end
+
+
+
+# lcodes(basename, trcount) -- deduce layer code(s) from file basename
+
+procedure lcodes(basename, trcount)
+ local n, s, tr
+
+ map(basename) ? {
+ if move(4) & ="a" & move(2) & any('f') then {
+ # xxxxAllF.DDF is layer ll attribute file
+ s := move(-2)
+ }
+ else if ="tr01" & =("le" | "np") & (n := integer(move(2))) then {
+ # TR01LEnn.DDF (or NPnn) is a transportation layer in a 100K map
+ if trcount > 12 then
+ s := ["mt", "rd", "rd", "rd", "rd", "rr"] [(n + 3) / 4]
+ else
+ s := ["mt", "rd", "rr"] [(n + 3) / 4]
+ }
+ else if move(2) & ="tr" & =("le" | "ne") & (n := integer(move(2))) then {
+ # xxTRLEnn.DDF (or NExx) is a transportation layer in state xx 250K map
+ s := ["mt", "rd", "rr"] [n % 3 + 1]
+ }
+ else {
+ move(2)
+ if any(&letters) then
+ s := move(2) # xxllyyyy is layer ll for state xx
+ else
+ s := move(-2) # ll01xxxx is layer ll otherwise
+ }
+ }
+
+ return case s of {
+ "bd": "b" # boundaries (BD: boundaries)
+ "hp": "c" # contours (HP: hypsography)
+ "nv": "d" # sand etc. (NV: nonvegetative features)
+ "pl": "l" # land sections (PL: public lands)
+ "sm": "m" # markers (SM: survey markers)
+ "rd": "r" # roads (RD: roads)
+ "ms": "s" # structures (MS: manmade structures)
+ "rr": "t" # train tracks (RR: railroads)
+ "mt": "u" # utilities (MT: miscellaneous transportation)
+ "tr": "rtu" # transportatn (TR: transportation, shared by r/t/u)
+ "sc": "v" # vegetation (SC: surface cover)
+ "hy": "w" # water (HY: hydrology)
+ default: "o" # other
+ }
+end
+
+
+
+# getdata(r, p, l) -- get data vector l of layer r using file pattern p
+
+procedure getdata(r, p, l)
+ local ddfile, d, e, zone
+
+ ddfile := ddpopen(r, p) |
+ stop("no file ", p, " for layer ", r.lcode, ": ", r.arg.name)
+ while d := ddfread(ddfile) do
+ every e := !d do
+ if e[1] == l then
+ break break
+ ddfclose(ddfile)
+ return e
+end
+
+
+
+# loadref(r) -- load XREF and IREF files for layer r of arg a
+
+procedure loadref(r)
+ local e
+
+ e := getdata(r, "*iref.ddf", "IREF")
+ until get(e) == "BI32"
+ r.xscale := real(get(e))
+ r.yscale := real(get(e))
+
+ e := getdata(r, "*xref.ddf", "XREF")
+ case e[5] of {
+ "NAS": r.datum := "NAD27" # North American 1927
+ "NAX": r.datum := "NAD83" # North American 1983
+ "WGA": r.datum := "WGS60" # World Geodetic System 1960
+ "WGB": r.datum := "WGS66" # World Geodetic System 1966
+ "WGC": r.datum := "WGS72" # World Geodetic System 1972
+ "WGE": r.datum := "WGS84" # World Geodetic System 1984
+ default: r.datum := "?????" # unrecognized
+ }
+ if e[4] == "UTM" then
+ r.zone := integer(e[6])
+ else
+ r.zone := -1
+
+ return
+end
+
+
+
+# loadbounds(r) -- load SPDM file to determine range of locations
+#
+# (SPDM files are used with 250K DLG layers)
+
+procedure loadbounds(r)
+ local e, xmn, xmx, ymn, ymx
+
+ e := getdata(r, "*spdm.ddf", "DMSA")
+ get(e)
+ xmn := get(e) * r.xscale * DEGSCALE
+ ymn := get(e) * r.yscale * DEGSCALE
+ xmx := get(e) * r.xscale * DEGSCALE
+ ymx := get(e) * r.yscale * DEGSCALE
+ r.ellipsoid := "Clarke66"
+ r.icorners := r.ocorners := [xmn, ymn, xmn, ymx, xmx, ymx, xmx, ymn]
+ return
+end
+
+
+
+# loadcorners(r) -- load NPnn file to determine corner points
+#
+# (NPnn files are used with 24K and 100K DLG layers)
+
+procedure loadcorners(r)
+ local ddfile, d, e, i, x, y, L, C66, G80
+
+ every ddfile := ddpopen(r, "*np??.ddf") do {
+ L := []
+ while d := ddfread(ddfile) do
+ every e := !d do
+ if get(e) == "SADR" then
+ while put(L, get(e))
+ ddfclose(ddfile)
+ r.icorners := cmerge(r.icorners, L)
+ }
+
+ if /r.icorners then
+ stop("no NPnn file for layer ", r.lcode, ": ", r.arg.name)
+
+ # infer ellipsoid of UTM projection
+ L := []
+ every i := 1 to *r.icorners by 2 do {
+ x := (r.icorners[i] * r.xscale - 500000.0)
+ y := (r.icorners[i+1] * r.yscale)
+ put(L, r.zone, x, y)
+ }
+ C66 := project(invp(utm("Clarke66")), L)
+ G80 := project(invp(utm("GRS80")), L)
+
+ if quadfit(C66) < quadfit(G80) then {
+ r.ellipsoid := "Clarke66"
+ r.ocorners := project(molodensky("NAD27", "NAD83"), C66)
+ }
+ else {
+ r.ellipsoid := "GRS80"
+ r.ocorners := G80
+ }
+
+ every !r.ocorners *:= DEGSCALE
+ return
+end
+
+
+
+# cmerge(A, B) -- merge two corners lists
+#
+# Assumes that the corner order is [SW, NW, NE, SE]
+# and takes the more extreme value for each coordinate.
+
+procedure cmerge(A, B)
+ local C
+
+ if /A | /B then return \A | \B
+ C := []
+
+ if A[1] + A[2] < B[1] + B[2] then
+ put(C, A[1], A[2])
+ else
+ put(C, B[1], B[2])
+
+ if A[3] - A[4] < B[3] - B[4] then
+ put(C, A[3], A[4])
+ else
+ put(C, B[3], B[4])
+
+ if A[5] + A[6] > B[5] + B[6] then
+ put(C, A[5], A[6])
+ else
+ put(C, B[5], B[6])
+
+ if A[7] - A[8] > B[7] - B[8] then
+ put(C, A[7], A[8])
+ else
+ put(C, B[7], B[8])
+
+ return C
+end
+
+
+
+# quadfit(L) -- proximity of coordinate in L to multiple of 1/8
+
+procedure quadfit(L)
+ local i, mn, mx, a, b
+
+ mn := 1.0
+ every i := 1 to *L by 2 do {
+ a := L[i] * 8
+ b := L[i+1] * 8
+ mx := max(abs(a - round(a)), abs(b - round(b)))
+ mn := min(mn, mx)
+ }
+ return mn
+end
+
+
+
+# lstats(a) -- report statistics for the layers of arg a
+
+procedure lstats(a)
+ local c, d, g, k, l, n, r, v, z
+
+ if \opts["q"] then
+ return
+
+ # group by identical projection attributes
+ g := table('')
+ every r := !a.ltable do {
+ k := lsumm(r)
+ g[k] ++:= r.lcode
+ }
+
+ # report consistent layers together on one line
+ l := sort(g, 3)
+ while k := get(l) do {
+ v := get(l)
+ writes(if /a.wanted then "X" else " ")
+ writes(if *g = 1 then " " else "*")
+ every c := !cset(DLG_LAYERS) do # list alphabetically
+ writes(if upto(v, c) then c else "-")
+ write(" ", k)
+ }
+ return
+end
+
+
+
+# lsumm(r) -- return one-line layer info summary
+
+procedure lsumm(r)
+ return r.datum[1] || r.datum[-2:0] || " " ||
+ (if r.zone < 0 then "LL" else right(r.zone, 2)) || " " ||
+ r.ellipsoid[1] || r.ellipsoid[-2:0] || " " ||
+ right(degc(r.ocorners[-1]), 3) || " " ||
+ left(degc(r.ocorners[-2]), 4) || " " ||
+ r.arg.name
+end
+
+
+
+# degc(d) -- code degree measurement as nnnx where x is a-h for 0/8 to 7/8
+
+procedure degc(d)
+ local n, x
+
+ d := abs(d / DEGSCALE) + 0.0625 # 1/16 for rounding
+ n := integer(d)
+ x := "abcdefgh" [1 + integer(8 * (d - n))]
+ return n || x
+end
+
+
+
+# field(constr, key) -- given record constructor, find index of named field
+
+procedure field(constr, key)
+ local i, r
+ image(constr) ? ="record constructor" | fail
+ r := constr()
+ every i := 1 to *r do
+ r[i] := i
+ return r[key]
+end
+
+
+
+# traversed(r) -- check whether layer r is traversed by a path
+
+procedure traversed(r)
+ local k, i, segs, pts, xmin, xmax, ymin, ymax
+
+ xmin := xmax := r.ocorners[1]
+ every xmin >:= r.ocorners[3 | 5 | 7]
+ every xmax <:= r.ocorners[3 | 5 | 7]
+ ymin := ymax := r.ocorners[2]
+ every ymin >:= r.ocorners[4 | 6 | 8]
+ every ymax <:= r.ocorners[4 | 6 | 8]
+
+ every k := key(attrib) do
+ if k ? (="g" & tab(many(&digits)) & pos(0)) then
+ every pts := !attrib[k].segs do
+ every i := 1 to *pts by 2 do
+ if xmin < pts[i] < xmax & ymin < pts[i+1] < ymax then
+ return
+
+ fail
+end
+
+
+
+# loadlayer(r) -- load one layer of files
+
+procedure loadlayer(r)
+ local p, attid, ddfile
+
+ setdraw(attrib[r.lcode])
+ drawlabel(r)
+
+ attid := table()
+ every ddfile := ddpopen(r, "*a??f.ddf") do {
+ loadatts(ddfile, r, attid)
+ ddfclose(ddfile)
+ }
+
+ every ddfile := ddpopen(r, "*ne??.ddf" | "*le??.ddf") do {
+ loadpts(ddfile, r, attid)
+ ddfclose(ddfile)
+ }
+
+ return
+end
+
+
+
+# loadatts(ddfile, r, attid) -- load attribute ID table
+
+procedure loadatts(ddfile, r, attid)
+ local d, e, i, k, n, s, v
+
+ n := -1
+ if r.lcode == "t" then
+ i := [1, 7] # for RR, append tunnel and rapid transit flags
+ else
+ i := []
+
+ while d := ddfread(ddfile) do {
+ k := &null
+ every e := !d do {
+ s := get(e)
+ if s == "ATPR" then
+ k := get(e) || get(e)
+ else if s == "ATTP" then {
+ v := get(e)
+ every \v ||:= (" " ~== e[!i])
+ attid[\k] := v
+ if (n +:= 1) % PINTERVAL = 0 then
+ progress(r)
+ }
+ }
+ }
+ return
+end
+
+
+
+# loadpts(ddfile, r, attid) -- load coordinate file into memory
+
+procedure loadpts(ddfile, r, attid)
+ local a, d, e, i, k, m, n, p, s, v, vv, x, y
+ local lcode, zone, coords, arec
+
+ lcode := r.lcode
+ zone := r.zone
+
+ if zone >= 0 then { # if not already in lat/lon form
+ if /opts["p"] then { # if no -p option
+ p := pptrans(r.icorners, r.ocorners) # use approx, faster projection
+ zone := &null # indicate such for code below
+ }
+ else {
+ p := invp(utm(r.ellipsoid)) # use full inverse-UTM projection
+ if r.ellipsoid == "Clarke66" then # and if needed,
+ p := compose(molodensky("NAD27", "NAD83"), p) # datum conversion
+ }
+ }
+
+ n := 0
+ while d := ddfread(ddfile) do {
+ a := lcode || "-"
+ v := []
+ coords := []
+ every e := !d do {
+ if *e < 3 then
+ next
+ s := get(e)
+ if s == "ATID" then {
+ k := get(e) || get(e)
+ while k[4] ~== "F" do
+ k := get(e) || get(e) | break
+ a := \attid[k] | lcode
+ }
+ else if s == "SADR" then {
+ if /p then {
+ # latitude/longitude input
+ while x := get(e) & y := get(e) do
+ put(v, x * r.xscale * DEGSCALE, y * r.yscale * DEGSCALE)
+ }
+ else if /zone then {
+ # using approximate projection, which includes scaling
+ while x := get(e) & y := get(e) do
+ put(coords, x, y)
+ }
+ else {
+ # full inverse UTM projection
+ while x := get(e) & y := get(e) do
+ put(coords, zone, x * r.xscale - 500000.0, y * r.yscale)
+ }
+ }
+ }
+
+ if \p then { # if projection needed
+ coords := project(p, coords) # project UTM to lat/lon
+ m := if /zone then 1 else DEGSCALE # select multiplier
+ while put(v, integer(m * get(coords))) # convert to scaled integer
+ }
+
+ if *v = 0 then
+ next
+
+ if not (arec := \attrib[a]) then {
+ # add unrecognized attribute code to table
+ arec := copy(attrib[lcode])
+ arec.key := a || "*" # "*" indicates unregistered attribute
+ arec.segs := []
+ attrib[a] := arec
+ }
+
+ while *v > MAXDRAW do { # break too-large path into pieces
+ vv := []
+ every 3 to MAXDRAW by 2 do
+ put(vv, get(v), get(v)) # move out of v
+ put(vv, v[1], v[2]) # leave one point for overlap
+ put(arec.segs, vv) # store extracted piece
+ }
+
+ # loops are rare in the data, but can crash XFree86 server if dashed
+ if v[1] = v[-2] & v[2] = v[-1] then { # if loop
+ put(v, v[3], v[4]) # overshoot to 2nd point again
+ }
+ put(arec.segs, v) # store what's left of original
+
+ if (n +:= 1) % PINTERVAL = 0 then
+ progress(r)
+
+ }
+ return
+end
+
+
+
+# logfeat() -- record current location to log file
+
+procedure logfeat(e)
+ local ll, lat, lon, locn, label
+
+ until Event() === (&lrelease | &rrelease) # wait for button up
+ ll := project(invp(mapproj()), [&x + 0.5, &y + 0.5]) # cvt coords to lat/lon
+ lon := get(ll) / DEGSCALE
+ lat := get(ll) / DEGSCALE
+ locn := frn(lat, 0, 6) || " " || frn(lon, 0, 6)
+ label := ""
+
+ if e === &lpress then { # if named (not anonymous), ask for label
+ setdraw(attrib["DIALOG"])
+ VSetFont()
+ case TextDialog(
+ ["Enter label for", locn || ":"], , , 30, ["Okay", "Cancel"]) of {
+ "Okay": label := " " || get(dialog_value)
+ "Cancel": fail
+ }
+ put(features, feature(DEGSCALE * lat, DEGSCALE * lon, label[2:0]))
+ if any(chosen, "f") then
+ allfeats(mapproj(), Pending) # redraw feats to display label
+ }
+
+ write(logfile, locn, label)
+ flush(logfile)
+ return
+end
+
+
+
+# locate() -- display location while right button is held down
+
+$define BOXW 265 # popup box width
+$define BOXH 90 # popup box height
+$define SMAX (BOXW - 40) # maximum scalebar length
+
+procedure locate()
+
+ setdraw(attrib["DIALOG"]) # set colors and font for drawing
+ Font("mono,bold,14")
+
+ if &x < BOXW + 40 & &y < BOXH + 40 then
+ Popup(20, WAttrib("height") - BOXH - 20, BOXW, BOXH, locproc, mapproj())
+ else
+ Popup(20, 20, BOXW, BOXH, locproc, mapproj())
+
+ return
+end
+
+
+
+# locate(wproj) -- calculate scale and location using caller's projection
+
+procedure locproc(wproj)
+ local d, e, m, s, u, cx, dx, dy, ll, lat, lon, winv
+
+ winv := invp(wproj) # get projection from screen to lat/lon
+ dx := WAttrib("dx") # get popup box coordinate system
+ dy := WAttrib("dy")
+
+ # compute a reasonably round length that works for a scale bar
+ u := 90 * DEGSCALE / 1e7 # one meter, in latitude units
+ m := sbsize(wproj, xmin, ymin, u, SMAX)
+
+ # draw the scale bar
+ ll := project(wproj, [xmin, ymin, xmin + m * u, ymin])
+ d := ll[3] - ll[1]
+ cx := BOXW / 2
+ FillRectangle(cx - d / 2, 55, d, 8)
+
+ if m >= 1000 then
+ s := (m / 1000) || " km"
+ else
+ s := m || " m"
+ CenterString(cx, 70, s)
+
+ # give coordinates of mouse location until button released
+ until e === &rrelease do {
+
+ ll := project(winv, [&x + 0.5, &y + 0.5]) # cvt screen coords to lat/lon
+ lon := get(ll) / DEGSCALE # and scale from integer to real
+ lat := get(ll) / DEGSCALE
+
+ GotoRC(1, 1)
+ WWrites("\n ", dms(lat, "S", "N"), frn(lat, 13, 6))
+ WWrites("\n ", dms(lon, "W", "E"), frn(lon, 13, 6))
+
+ e := Event() # get next event
+ &x +:= dx # remove effect of popup box coordinate system
+ &y +:= dy
+ }
+
+ return
+end
+
+procedure dms(n, s1, s2)
+ local deg, min, sec
+
+ if n < 0 then
+ n := -n
+ else
+ s1 := s2
+
+ n +:= 1 / 7200. # rounding
+ deg := integer(n); n := (n - deg) * 60
+ min := integer(n); n := (n - min) * 60
+ sec := integer(n)
+
+ return s1 || right(deg, 4) || "\260" || right(min, 2, "0") || "'" ||
+ right(sec, 2, "0") || "\""
+end
+
+
+
+# draw(win, pjn) -- draw all selected map layers, without erasing first
+
+procedure draw(win, pjn)
+ local a, d, v, arec
+
+ every (arec := !slist) & any(chosen, arec.lcode) do {
+ setdraw(arec) | next
+ every d := !arec.segs do {
+ v := project(pjn, d) # project to window x/y coords
+ every !v >:= 30000.0 # clamp to legal X values allowing dx/dy
+ every !v <:= -30000.0 # clamp as floating to avoid lgint bug
+ if *v = 2 then
+ FillRectangle(v[1] - 1, v[2] - 1, 3, 3)
+ else
+ DrawLine ! v
+ if *Pending() > 0 then
+ return
+ }
+ }
+
+ # draw feature (waypoint) labels
+ if any(chosen, "f") then
+ allfeats(pjn, Pending)
+
+ # draw pseudo-layer "n"
+ if any(chosen, "n") then
+ alllabels(Pending)
+
+ collect() # do this now, while awaiting input
+ return
+end
+
+
+
+# winit() -- initialize window configuration
+
+procedure winit()
+ local a
+
+ xmin := ymin := +180 * DEGSCALE
+ xmax := ymax := -180 * DEGSCALE
+ every a := !arglist do
+ if \a.wanted then {
+ every xmin >:= (!a.ltable).ocorners[1 | 3]
+ every xmax <:= (!a.ltable).ocorners[5 | 7]
+ every ymin >:= (!a.ltable).ocorners[2 | 8]
+ every ymax <:= (!a.ltable).ocorners[4 | 6]
+ }
+ aspect := cos(dtor((ymax + ymin) / (2 * DEGSCALE)))
+ return
+end
+
+
+
+# allfeats(pjn, p) -- draw feature labels
+#
+# p is Pending procedure, if to check and quit early
+
+procedure allfeats(pjn, p)
+ local f, x, y, xy, xy2
+
+ xy := []
+ every f := !features do
+ put(xy, f.lon, f.lat)
+ xy := project(pjn, xy)
+ xy2 := copy(xy)
+
+ Font("sans, bold, 10")
+ setdraw(attrib["f"])
+ Fg("white") # draw offset backgrounds in white
+ every f := !features do {
+ DrawString(get(xy2) + 4, get(xy2) + 5, f.label)
+ if *(\p)() > 0 then
+ break
+ }
+
+ setdraw(attrib["f"]) # draw labels in black
+ every f := !features do {
+ x := get(xy)
+ y := get(xy)
+ FillRectangle(x - 1, y - 1, 3, 3)
+ DrawString(x + 5, y + 4, f.label)
+ if *(\p)() > 0 then
+ break
+ }
+
+ return
+end
+
+
+
+# alllabels(p) -- draw labels for all layers in standard color
+#
+# p is Pending procedure, if to check and quit early
+
+procedure alllabels(p)
+ local a, r
+
+ setdraw(attrib["n"])
+ every a := !arglist do {
+ if \a.wanted then {
+ drawlabel(!a.ltable) # pick any layer
+ if \opts["c"] then {
+ drawcoverage(a)
+ setdraw(attrib["n"])
+ }
+ }
+ if *(\p)() > 0 then
+ break
+ }
+ return
+end
+
+
+
+# drawlabel(r) -- draw label for layer r in current color
+#
+# sets r.px, r.py to progress bar position and r.wd to layer width
+
+procedure drawlabel(r)
+ local x, y, w, h, n, d, u, s, tw, tmax, v, wproj
+ static lc, uc
+ initial {
+ lc := string(&lcase)
+ uc := string(&ucase)
+ }
+
+ # draw the bounding box
+ wproj := mapproj()
+ v := copy(r.ocorners)
+ put(v, r.ocorners[1], r.ocorners[2])
+ v := project(wproj, v) # project to window x/y coords
+ every !v >:= 30000.0 # clamp to legal X values allowing dx/dy
+ every !v <:= -30000.0 # clamp as floating to avoid lgint bug
+ DrawLine ! v
+
+ # find the center and range
+ x := (v[1] + v[3] + v[5] + v[7]) / 4
+ y := (v[2] + v[4] + v[6] + v[8]) / 4
+ w := (v[5] + v[7] - v[1] - v[3]) / 2
+ h := (v[4] + v[6] - v[2] - v[8]) / 2
+
+ # trim the name
+ s := r.arg.name
+ while s[-1] == "/" do
+ s := s[1:-1]
+ s ? {
+ while tab(upto('/') + 1)
+ s := map(tab(0), lc, uc)
+ }
+ if s[-4:0] == (".ZIP" | ".GPS" | ".RTE" | ".TRK") then
+ s := s[1:-4]
+
+ # draw the label
+ Font("sans,bold," || MAXFONT)
+ tw := TextWidth(s)
+ tmax := .90 * w
+ if tw > tmax then {
+ n := integer(MAXFONT * tmax / tw)
+ if n <:= MINFONT then {
+ # it doesn't fit, and will overlap neighbors with minimum font size;
+ # add pseudorandom vertical offset to mitigate overlap
+ d := abs(r.ocorners[7] / DEGSCALE) # SE corner longitude
+ u := integer(8 * d + 0.5) # 1/8-degree units
+ u +:= integer(2 * d + 0.5) # half-degree units
+ y -:= 0.20 * h * (1.5 - u % 4)
+ }
+ if n < MINBOLD then
+ Font("sans," || n)
+ else
+ Font("sans,bold," || n)
+ }
+ CenterString(x, y, s)
+
+ r.px := integer(x)
+ r.py := integer(y + 0.75 * WAttrib("fheight"))
+ r.wd := w
+ return
+end
+
+
+
+# progress(r) -- draw progress square for layer r
+
+procedure progress(r)
+ local a, x
+
+ a := r.arg
+ a.pcount := (\a.pcount + 1) | 0
+ x := r.px + PSQSIZE * (a.pcount % PSQUARES - PSQUARES / 2)
+ FillRectangle(x, r.py, PSQSIZE - PSQGAP, PSQSIZE - PSQGAP)
+ if (a.pcount / PSQUARES) % 2 = 1 then
+ EraseArea(x + 1, r.py + 1, PSQSIZE - PSQGAP - 2, PSQSIZE - PSQGAP - 2)
+ return
+end
+
+
+
+# drawcoverage(a) -- draw coverage indicators for arg entry a
+
+procedure drawcoverage(a)
+ local c, r, x, y, w
+
+ r := \!a.ltable | return
+ w := r.wd / *DLG_LAYERS
+ w >:= PSQSIZE
+ w <:= 2
+ x := r.px - (w * *DLG_LAYERS) / 2
+ y := r.py
+
+ every c := !cset(DLG_LAYERS) do {
+ if r := \a.ltable[c] then {
+ setdraw(attrib[r.lcode])
+ FillRectangle(x, y, w, w)
+ }
+ x +:= w
+ }
+ return
+end
+
+
+
+# print() -- print visible portion to file
+
+procedure print()
+ local psname, psfile
+
+ Bg("pale weak brown")
+ VSetFont()
+ setdraw(attrib["DIALOG"]) # set reasonable colors for dialog
+ repeat case OpenDialog("Print to file:") of {
+ "Okay": {
+ if *dialog_value = 0 then
+ next
+ if close(open(psname := dialog_value)) then
+ case TextDialog("Overwrite existing file?", , , ,
+ ["Yes", "No", "Cancel"]) of {
+ "Yes": &null
+ "No": next
+ "Cancel": fail
+ }
+ if psfile := open(psname, "w") then
+ break
+ case TextDialog("Cannot write " || psname) of {
+ "Okay": next
+ "Cancel": fail
+ }
+ }
+ "Cancel":
+ fail
+ }
+
+ Popup(, , 300, 50,
+ popwrite, [psfile, mapproj(), WAttrib("width"), WAttrib("height")])
+ close(psfile)
+ return
+end
+
+procedure popwrite(psargs)
+ CenterString(150, 25, "Writing PostScript...")
+ return writeps ! psargs
+end
+
+procedure writeps(psfile, projn, wwidth, wheight)
+ local arec, color, style, width, ptoff, xmax, ymax, xmul, ymul
+ local a, b, f, m, w, h, pj, d, s, u, v, x, y, dx, dy, fx, fy, ll
+
+ b := project(invp(projn), [0, 0, wwidth, wheight])
+ xmax := PSSCALE * wwidth
+ ymax := PSSCALE * wheight
+ xmul := xmax / (b[3] - b[1])
+ ymul := ymax / (b[2] - b[4])
+ pj := rectp(b[1], b[4], 0, 0, xmul, ymul) # set projection
+
+ ptoff := PSPT / 2
+ s := " 0 " || PSPT || " rlineto"
+ s ||:= " " || PSPT || " 0 rlineto"
+ s ||:= " 0 -" || PSPT || " rlineto"
+
+ epsheader(psfile, 0, 0, PSSCALE * wwidth, PSSCALE * wheight, "r")
+ every write(psfile, ![
+ "1 setlinecap",
+ "/cdivr { 65535 div 3 1 roll } bind def",
+ "/color { cdivr cdivr cdivr setrgbcolor } bind def",
+ "/solid { [] 0 setdash } bind def",
+ "/dashed { [ .04 inch dup ] 0 setdash } bind def",
+ "/m { moveto } bind def",
+ "/r { rlineto } bind def",
+ "/s { rlineto stroke } bind def",
+ "/p { moveto" || s || " fill } bind def",
+ "/f { 2 copy p moveto 48 -36 rmoveto show } bind def",
+ ])
+
+ every (arec := !slist) & any(chosen, arec.lcode) do {
+ if *arec.segs = 0 | arec.width < 0 then
+ next
+ if color ~===:= arec.color then
+ write(psfile, map(ColorValue(arec.color), ",", " "), " color")
+ if width ~===:= arec.width then
+ write(psfile, arec.width / real(PSLWI), " inch setlinewidth")
+ if style ~===:= arec.style then
+ write(psfile, style)
+ every d := !arec.segs do {
+ v := project(pj, d)
+ if *v = 2 then {
+ x := integer(get(v))
+ y := integer(get(v))
+ if (0 <= x < xmax) & (0 <= y < ymax) then
+ write(psfile, x - ptoff, " ", y - ptoff, " p")
+ next
+ }
+ v := Coalesce(ClipLine(v, 0, 0, xmax, ymax)) | next
+ every a := !v do {
+ x := integer(get(a))
+ y := integer(get(a))
+ fy := integer(pull(a))
+ fx := integer(pull(a))
+ write(psfile, x, " ", y, " m")
+ while dx := integer(get(a) - x) do {
+ dy := integer(get(a) - y)
+ write(psfile, dx, " ", dy, " r")
+ x +:= dx
+ y +:= dy
+ }
+ write(psfile, fx - x, " ", fy - y, " s")
+ }
+ }
+ }
+
+ # write features
+ if *features > 0 & any(chosen, "f") then {
+ write(psfile)
+ write(psfile, "/Times-Roman findfont 120 scalefont setfont")
+ write(psfile, "0 0 0 color")
+ every f := !features do {
+ a := project(pj, [f.lon, f.lat])
+ x := integer(get(a))
+ y := integer(get(a))
+ if (0 <= x <= xmax) & (0 <= y <= ymax) then
+ write(psfile, "(", psprotect(f.label), ") ",
+ x - ptoff, " ", y - ptoff, " f")
+ }
+ }
+
+ # write scale bar
+ u := 90 * DEGSCALE / 1e7 # one meter, in latitude units
+ m := sbsize(pj, xmin, ymin, u, 2000)
+ ll := project(pj, [xmin, ymin, xmin + m * u, ymin])
+ d := ll[3] - ll[1]
+ if m >= 1000 then
+ s := (m / 1000) || " km"
+ else
+ s := m || " m"
+
+ every write(psfile, ![
+ "",
+ "0 0 0 color",
+ "0 0 m 0 120 r " || d || " 0 r 0 -120 r fill",
+ "/Helvetica findfont 100 scalefont setfont",
+ "65535 65535 65535 color",
+ integer(d / 2 - 120) || " 25 m (" || s || ") show",
+ ])
+
+ write(psfile, "showpage")
+ return
+end
+
+
+
+
+# initattrib() -- initialize drawing attributes
+#
+# IMPORTANT: Map entities are drawn in the order of the def() calls below.
+
+procedure initattrib()
+ local i, s
+
+$define ROUTE "magenta-red" # path foreground color
+ pcolors := [ # path background colors
+ "yellow", # yellow
+ "light green", # green
+ "light bluish cyan", # blue
+ "reddish yellow", # orange
+ "pale purple", # purple
+ "pale red-yellow", # peach
+ "pale moderate green", # greenish gray
+ "pale moderate cyan", # bluish gray
+ ]
+ pull(pcolors) # remove trailing null
+
+ attrib := table()
+ deflayer(" ", "black")
+ def("SWEEP", 3, "reddish orange") # interactive sweeping with mouse
+ def("DIALOG", 1, "black") # dialog boxes
+
+ every i := 1 to *pcolors do {
+ s := "g" || i
+ deflayer(s, ROUTE) # paths (first drawing)
+ def(s || "b", 10, pcolors[i]) # faint, wide highlighter background
+ def(s || "f", 2, ROUTE) # bold foreground
+ }
+
+ deflayer("b", "light reddish yellow") # boundaries (wide, so draw first)
+ def("b", 3)
+
+ deflayer("o", Fg()) # unknown other data; use specified Fg
+ def("o")
+
+ deflayer("c", "light red-yellow") # contour lines (hypsography)
+ def("c-", , "pale moderate red-yellow") # deemphasize unattributed segments
+ def("c") # contour line
+ def("0200205", , "light moderate bluish-cyan") # bathymetric contour
+ def("0200206", , "light moderate bluish-cyan") # depth curve
+ def("0200210", , "light moderate bluish-cyan") # suppl bathymetric contour
+ def("0200207", , "deep red-yellow") # watershed (e.g. continental) divide
+
+ deflayer("l", "pale whitish red") # land sections
+ def("l")
+
+ deflayer("v", "light green") # vegetation (surface cover)
+ def("v") # surface cover
+
+ deflayer("d", "light weak green") # gravel etc. (nonvegetative features)
+ def("d")
+
+ deflayer("w", "bluish cyan") # water (hydrology)
+ def("w-", , "pale bluish cyan") # deemphasize unattributed segments
+ def("0500415", , , "dashed") # aqueduct or water pipeline
+ def("0500200", 2) # shoreline
+ def("0500201", 2) # manmade shoreline
+ def("w") # unspecified hydrology
+ def("0500412") # stream
+
+ deflayer("s", "weak reddish yellow") # manmade structures
+ def("2000299", , "pale reddish yellow") # processing line
+ def("s")
+ def("s-") # uattributed, incl building outlines
+ def("2000400") # buildings as point nodes
+ def("2000202", , "light moderate reddish yellow") # wall
+ def("2000206", , "light moderate reddish yellow") # fence
+
+ deflayer("r", "deep gray") # roads and trails
+ def("r-", , "pale gray") # deemphasize unattributed segments
+ def("1700201", 3, "black") # road, primary, undivided
+ def("1700202", 3, "black") # road, primary, divided
+ def("1700203", 2, "black") # road, primary, one of divided paths
+ def("1700204", 2, "black") # road, primary, one-way
+ def("1700205", 2, "black") # road, secondary
+ def("1700206", 2, "black") # road, secondary
+ def("1700207", 2, "black") # road, secondary
+ def("1700208", 2, "black") # road, secondary
+ def("1700214", 1, "black", "dashed") # ferry route
+ def("1700218") # road, class 3, divided
+ def("1700209") # road, class 3, undivided
+ def("1700402") # entrance ramp
+ def("r") # unspecified road or trail
+ def("1700210", , , "dashed") # road, class 4
+ def("1700219", , , "dashed") # road, class 4, one-way
+ def("1700212", , , "dashed") # road, class 5, 4WD
+ def("1700211", , "dark red", "dashed") # trail
+ def("1700213", , "dark red", "dashed") # footbridge
+
+ deflayer("t", "dark orange") # railroads
+ def("t-", , "pale weak orange") # deemphasize unattrib segments
+ def("t") # unspecified railroad
+ def("1800201", 2) # railroad main
+ def("1800201E", 2) # railroad main elevated
+ def("1800201R", 2) # railroad main on drawbridge
+ def("1800201T", 2, , "dashed") # railroad main in tunnel
+ def("1800207", 1, , "dashed") # railroad ferry route
+ def("1800208", 1) # railroad siding
+ def("1800209", 2) # railroad yard
+ def("1800400", 1) # railroad station
+$define TRANSIT "dark blue"
+ def("1800201Y", 2, TRANSIT) # rapid transit rail main
+ def("1800201EY", 2, TRANSIT) # rapid transit main elevated
+ def("1800201RY", 2, TRANSIT) # rapid transit main on drawbrg
+ def("1800201TY", 2, TRANSIT, "dashed") # rapid transit main in tunnel
+ def("1800202Y", 2, TRANSIT) # rapid transit main in road
+ def("1800202RY", 2, TRANSIT) # rapid transit, in road on drawbridge
+ def("1800208Y", 1, TRANSIT) # rapid transit siding
+ def("1800400Y", 1, TRANSIT) # rapid transit station
+
+ deflayer("u", "light gray") # misc transpt: power, pipe, airport
+ def("u")
+ def("u-", , "white-gray") # unattrib segments incl airport runways
+$define UTILITY "strong purple-magenta"
+ def("1900201", 1, UTILITY, "dashed") # petroleum pipeline
+ def("1900202", 1, UTILITY) # power line
+ def("1900203", 1, UTILITY) # phone line
+ def("1900400", 1, UTILITY) # power plant
+ def("1900401", 1, UTILITY) # substation
+ def("1900402", 1, UTILITY) # hydro plant
+ def("1900403", 1, "light gray") # landing strip or airport
+ def("1900404", 1, "orange") # helipad
+ def("1900405", 1, "light gray") # launch complex
+
+ deflayer("m", "blue") # survey markers
+ def("m-", , "pale weak blue") # deemphasize unattributed lines
+ def("m")
+
+ deflayer("f", "black") # feature labels
+ def("f")
+
+ deflayer("n", "deep green") # file labels
+ def("n")
+
+ deflayer("g", ROUTE) # paths (retraced)
+
+ every i := 1 to *pcolors do { # link ea GPS bg/fg/bg set to one list
+ s := "g" || i
+ def(s, 2)
+ attrib[s || "b"].segs := attrib[s].segs
+ attrib[s || "f"].segs := attrib[s].segs
+ }
+
+ return
+end
+
+
+
+# deflayer -- define layer code and default color for subsequent defs
+
+global layercode, layercolor
+
+procedure deflayer(lcode, color)
+ layercode:= lcode
+ layercolor := color
+ return
+end
+
+
+
+# def(key, width, color, style) -- define style info for code or attribute
+#
+# default width is 1
+# default color is as last set by deflayer()
+# default style is "solid"
+#
+# a key of "x" matches undefined attributes of layer x
+# a key of "x-" matches segments without attributes
+#
+# a width of -1 means "don't draw"
+
+procedure def(key, width, color, style)
+ static seq
+ initial seq := 0
+
+ /width := 1
+ /color := layercolor
+ /style := "solid"
+ attrib[key] := attrec(seq +:= 1, layercode, key, width, color, style, [])
+ return
+end
+
+
+
+# setdraw(arec) -- set color, linewidth, linestyle based on attribute record
+#
+# fails if width is negative, meaning that drawing is to be suppressed
+
+procedure setdraw(arec)
+ if arec.width < 0 then
+ fail
+ WAttrib("fg=" || arec.color,
+ "linewidth=" || arec.width, "linestyle=" || arec.style)
+ return
+end