summaryrefslogtreecommitdiff
path: root/ipl/gpacks/tiger/tgrmap.icn
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/gpacks/tiger/tgrmap.icn')
-rw-r--r--ipl/gpacks/tiger/tgrmap.icn978
1 files changed, 978 insertions, 0 deletions
diff --git a/ipl/gpacks/tiger/tgrmap.icn b/ipl/gpacks/tiger/tgrmap.icn
new file mode 100644
index 0000000..b5dbbe2
--- /dev/null
+++ b/ipl/gpacks/tiger/tgrmap.icn
@@ -0,0 +1,978 @@
+############################################################################
+#
+# File: tgrmap.icn
+#
+# Subject: Program to generate map from TIGER files
+#
+# Authors: Gregg M. Townsend and William S. Evans
+#
+# Date: July 29, 2000
+#
+############################################################################
+#
+# Tgrmap draws maps based on TIGER data files from the Census Bureau.
+# Data files must be in "line chain" (.lch) format as written by the
+# associated "tgrprep" program.
+#
+# Usage: tgrmap [file.lch ...]
+# Input is zero or more files of chains created by "tgrprep.icn".
+#
+# All manipulation is done by mouse actions, keyboard shortcuts, or
+# window resizing. There are no menus (although they would be nice
+# to have.)
+#
+# Mouse actions:
+# Sweeping an area with the left mouse button zooms the image
+# to display that area better. To cancel a sweep, just reduce
+# the swept height or width to less than 10 pixels.
+#
+# Clicking the center mouse button pops up the Layers dialog,
+# which selects the categories of data to be shown or hidden.
+# No other actions are accepted while the dialog box is up.
+# (This only works on 8-bit displays, a vanishing breed.)
+#
+# Clicking the right mouse button when the cursor is on a line
+# brings up a subwindow that shows the name and type of the
+# line.
+#
+# Keyboard shortcuts:
+# F find a feature by name
+# L bring up the layers dialog
+# M create PPM image
+# O open a new file
+# P create PostScript file for printing
+# Q quit
+# R refresh the display
+# S save the displayed data to file
+# + zoom in by a factor of 2
+# - zoom out by a factor of 2
+# 2-9 zoom to factor given
+# 1 reset original map (centered and unzoomed)
+# arrow arrow keys shift the center of the displayed area
+#
+# Window resizing is allowed.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: clipping, graphics, numbers, pscript, strings
+#
+############################################################################
+
+# Ideas for future changes:
+# Add menu alternatives to keyboard shortcuts
+# Write *color* PostScript, at least as an option
+# (the programming is easy; tuning the colors is the hard part)
+
+link clipping
+link graphics
+link numbers
+link pscript
+link strings
+
+$include "keysyms.icn"
+
+$ifndef _X_WINDOW_SYSTEM
+ $define Key_KP_Up Key_Up
+ $define Key_KP_Down Key_Down
+ $define Key_KP_Left Key_Left
+ $define Key_KP_Right Key_Right
+$endif
+
+$define MARGIN 5 # margin around full-sized map
+$define CLIP 2 # clipping margin, allowing for linewdth
+$define SHIFTBY 32 # number of pixels to shift at once
+$define PSSCALE 100 # scaling from pixels to PostScript
+$define MAXDRAW 4000 # maximum (even) args to avoid error 301
+$define EPSILON 2.5 # how close is enough when clicking
+
+# file values
+global ifileList, fnameList # input files and their names
+global lonmin, lonmax, latmin, latmax # input range
+
+# windows
+global wintbl # table of GCs by type
+global msgwin # base window for notices
+global title # window title
+global tmpwin # temp window for PPM snapshots
+
+# window parameters
+global dx, dy # current translation values
+global fullx, fully # scaling for zoom-1 display
+
+# display parameters
+global ctrlon, ctrlat # longitude/latitude of center
+global curzoom, xscale, yscale # current zoom factor and scaling
+global lonrange, latrange # distance from center to edge
+
+# inquiry parameters
+global litName # string to match against feature name
+
+
+# the classification list finds things based on line type
+record class( # classification record
+ prefix, # CFCC prefix
+ psgray, # PostScript graylevel
+ pswidth, # PostScript linewidth
+ label, # label
+ color, # color
+ width, # line width
+ index, # mutable color index
+ vispfx) # prefix code, but only if visible
+global clist # list of classification records
+global ctable # table keyed by two-char prefix
+
+
+
+procedure main(args)
+ local e, xywh, lon1, lat1, lon2, lat2, hilightOnly
+
+ Window("size=870,870", "bg=pale moderate brown", args)
+
+ &error := 1
+ WAttrib("resize=on")
+ &error := 0
+
+ Font(("Helvetica" | "Univers" | "LucidaSans") || ",bold,12") # may fail
+ WAttrib("pointer=cross" | "pointer=crosshair") # may fail
+ msgwin := Clone(&window) # save shaded window for notices
+ setclasses()
+
+ if *args > 0 then
+ setfiles(args) | exit()
+ else
+ setfiles() | exit()
+
+ setwindow()
+ setcenter()
+ setzoom()
+ drawmap()
+
+ repeat case e := Event() of {
+
+ !"01": {
+ setregion(lonmin, lonmax, latmin, latmax)
+ drawmap()
+ }
+
+ !"23456789": { setzoom(e); drawmap() }
+ !"+=": { setzoom(2.0 * curzoom); drawmap() }
+ "-": { setzoom(0.5 * curzoom); drawmap() }
+
+ !"Oo": {
+ if setfiles() then {
+ setwindow()
+ setcenter()
+ setzoom()
+ drawmap()
+ }
+ }
+
+ !"Ll": setlayers()
+ !"Rr": drawmap()
+
+ !"Mm": {
+ tmpwin := WOpen("canvas=hidden",
+ "width=" || WAttrib("width"), "height=" || WAttrib("height"))
+ if /tmpwin then {
+ Notice("can't open temporary canvas", "for PPM snapshot")
+ break
+ }
+ CopyArea(&window, tmpwin)
+ writefile(writeppm, "Write PPM file:", "Writing PPM file...")
+ WClose(tmpwin)
+ tmpwin := &null
+ }
+
+ !"Pp":
+ writefile(writeps, "Write PostScript file:", "Writing PostScript...")
+
+ !"Ss":
+ writefile(writelch, "Save displayed portion as:", "Saving...")
+
+ !"Ff": {
+ if /litName then {
+ hilightOnly := 1
+ litName := ""
+ }
+ else {
+ hilightOnly := &null
+ }
+ if TextDialog("Find features named:", , litName) == "Okay" then {
+ litName := map(dialog_value[1])
+ if litName == "" then litName := &null
+ drawmap(hilightOnly)
+ }
+ }
+
+ QuitEvents(): break
+
+ Key_Left | Key_KP_Left: shift(e, +SHIFTBY, 0)
+ Key_Right | Key_KP_Right: shift(e, -SHIFTBY, 0)
+ Key_Up | Key_KP_Up: shift(e, 0, +SHIFTBY)
+ Key_Down | Key_KP_Down: shift(e, 0, -SHIFTBY)
+
+ &lpress: {
+ xywh := Sweep()
+ if xywh[3|4] < 10 then
+ next
+ lon1 := ctrlon + (get(xywh) - 0.5) / xscale
+ lat1 := ctrlat + (get(xywh) - 0.5) / yscale
+ lon2 := lon1 + (get(xywh) + 0.5) / xscale
+ lat2 := lat1 + (get(xywh) + 0.5) / yscale
+ setregion(lon1, lon2, lat1, lat2)
+ drawmap()
+ }
+
+ &mrelease: {
+ setlayers()
+ }
+
+ &rrelease: {
+ identify(&x, &y)
+ }
+
+ &resize: {
+ resize()
+ }
+ }
+end
+
+
+procedure writefile(proc, caption, message)
+ local oname, ofile
+
+ repeat case OpenDialog(msgwin, caption) of {
+ "Okay": {
+ if *dialog_value = 0 then
+ next
+ if close(open(oname := dialog_value)) then
+ case TextDialog(msgwin, "Overwrite existing file?", , , ,
+ ["Yes", "No", "Cancel"]) of {
+ "Yes": &null
+ "No": next
+ "Cancel": fail
+ }
+ if ofile := open(oname, "w") then
+ break
+ case TextDialog(msgwin, "Cannot open " || oname) of {
+ "Okay": next
+ "Cancel": fail
+ }
+ }
+ "Cancel":
+ fail
+ }
+
+ Popup(msgwin, , , 32 + TextWidth(msgwin, message), 32,
+ popmsg, message, proc, ofile)
+ close(ofile)
+ return
+end
+
+procedure popmsg(message, proc, ofile)
+ CenterString(WAttrib("clipw") / 2, WAttrib("cliph") / 2, message)
+ return proc(ofile)
+end
+
+
+procedure setfiles(L)
+ local f, fname
+
+ /L := list()
+ fnameList := list()
+ every close(!(\ifileList))
+ ifileList := list()
+ prescan() # reset lonmin,lonmax,latmin,latmax
+ until *fnameList > 0 do {
+ until *L > 0 do {
+ case OpenDialog(msgwin, "Input file(s):") of {
+ "Okay": put(L, words(dialog_value))
+ "Cancel": fail
+ }
+ }
+ while fname := get(L) do {
+ if not (f := open(fname)) then {
+ Notice(msgwin, "Cannot open " || fname)
+ next
+ }
+ if not (prescan(f)) then {
+ Notice(msgwin, "Invalid format: " || fname)
+ close(f)
+ next
+ }
+ put(fnameList, fname)
+ put(ifileList, f)
+ }
+ }
+ return
+end
+
+
+
+# prescan(f) -- verify that f is a valid file, setting globals if so
+
+procedure prescan(f)
+ local line, alon, alat, blon, blat
+ if /f then {
+ lonmin := latmin := 9999999
+ lonmax := latmax := 0
+ return
+ }
+ line := read(f) | fail
+ line ? {
+ =" " | fail
+ alon := move(7) | fail
+ alat := move(7) | fail
+ }
+ line := read(f) | fail
+ line ? {
+ =" " | fail
+ blon := move(7) | fail
+ blat := move(7) | fail
+ }
+ if alon > blon then {
+ alon :=: blon
+ alat :=: blat
+ }
+ lonmin >:= alon
+ latmin >:= alat
+ lonmax <:= blon
+ latmax <:= blat
+ return
+end
+
+
+
+procedure setwindow()
+ local ww, wh, xstr, ystr, latsin, raspr, waspr
+
+ ww := WAttrib("width")
+ wh := WAttrib("height")
+ dx := ww / 2
+ dy := wh / 2
+
+ xstr := "dx=" || (dx := WAttrib("width") / 2)
+ ystr := "dy=" || (dy := WAttrib("height") / 2)
+ every WAttrib(&window | !wintbl, xstr, ystr)
+
+ # calculate aspect ratio of file region
+ latsin := sin(((latmax + latmin) / 2.0) * (&pi / 9999999))
+ raspr := real(lonmax - lonmin) / real(latmax - latmin) * latsin * (360 / 180)
+
+ # calculate aspect ratio of window
+ waspr := real(ww - 2 * MARGIN) / real(wh - 2 * MARGIN)
+
+ # calculate scaling for zoom factor of 1.0
+ if waspr > raspr then {
+ # window is too wide
+ fully := real(wh - 2 * MARGIN) / (latmax - latmin)
+ fullx := fully * latsin * (360 / 180)
+ }
+ else {
+ # window is too tall
+ fullx := real(ww - 2 * MARGIN) / (lonmax - lonmin)
+ fully := fullx / latsin / (360 / 180)
+ }
+ return
+end
+
+
+
+procedure setcenter(lon, lat)
+ ctrlon := round(\lon | (lonmin + lonmax) / 2.0)
+ ctrlat := round(\lat | (latmin + latmax) / 2.0)
+ return
+end
+
+
+
+procedure setzoom(n)
+ local x1, y1, x2, y2
+
+ curzoom := \n | 1.0
+ xscale := curzoom * fullx
+ yscale := curzoom * fully
+ lonrange := integer(dx / xscale + 0.5)
+ latrange := integer(dy / yscale + 0.5)
+
+ # clip out-of-bounds data because it's probably incomplete
+ x1 := integer((lonmin - ctrlon) * xscale - 0.5) - CLIP
+ x2 := integer((lonmax - ctrlon) * xscale + 0.5) + CLIP
+ y1 := integer((latmin - ctrlat) * yscale - 0.5) - CLIP
+ y2 := integer((latmax - ctrlat) * yscale + 0.5) + CLIP
+
+ # limit clipping bounds to sensible values, else X gets confused
+ x1 <:= -dx
+ x2 >:= dx
+ y1 <:= -dy
+ y2 >:= dy
+
+ # clip only drawing windows; NOT &window, used for copying and erasing!
+ every Clip(!wintbl, x1, y1, x2 - x1, y2 - y1)
+ return
+end
+
+
+
+procedure resize()
+ local dxold, dyold, xshift, yshift
+
+ dxold := dx # save old translation values
+ dyold := dy
+
+ setwindow() # set window parameters for new size
+
+ xshift := dx - dxold
+ yshift := dy - dyold
+
+ # move to realign existing map with new window center
+ CopyArea(-dx - xshift, -dy - yshift, 2 * dx, 2 * dy, -dx, -dy)
+ if xshift > 0 then EraseArea(dx - xshift, -dy)
+ if yshift > 0 then EraseArea(-dx, dy - yshift)
+
+ # restore scaling and clipping
+ setzoom(xscale / fullx) # don't change zoom, but reset other globals
+
+ return
+end
+
+
+
+procedure shift(e, nx, ny)
+
+ while Pending()[1] === e do
+ Event() # consume duplicate shift events
+
+ setcenter(ctrlon - nx / xscale, ctrlat - ny / yscale)
+ CopyArea(-dx, -dy, 2 * dx, 2 * dy, -dx + nx, -dy + ny)
+ if (nx > 0) then EraseArea(-dx, -dy, nx, 2 * dy)
+ if (ny > 0) then EraseArea(-dx, -dy, 2 * dx, ny)
+ if (nx < 0) then EraseArea(dx + nx, -dy)
+ if (ny < 0) then EraseArea(-dx, dy + ny)
+ settitle() # reset center coords in title
+ setzoom(curzoom) # reset clipping
+
+ drawmap()
+
+ return
+end
+
+
+
+procedure drawmap(hilightOnly)
+ local line, w, worig, lon, lat, dlon, dlat, a, bdy, dim, class, fename, f
+ local drawProc, litFeature
+
+ WAttrib("pointer=wait" | "pointer=watch")
+
+ if /hilightOnly then {
+ EraseArea()
+ settitle()
+ }
+ litFeature := list()
+
+ every f := !ifileList do {
+ seek(f, 1)
+ read(f) # skip minima line
+ read(f) # skip maxima line
+
+ while line := read(f) do line ? {
+
+ if *Pending() > 0 then {
+ WAttrib("pointer=cross" | "pointer=crosshair")
+ return
+ }
+
+ w := \wintbl[class := move(2)] | next
+ move(1)
+ bdy := move(1)
+ if ="|" then {
+ fename := tab(upto('|'))
+ move(1)
+ }
+ else {
+ fename := &null
+ }
+ dim := integer(move(4))
+ lon := move(7) - ctrlon
+ lat := move(7) - ctrlat
+
+# quick clip
+ if dim < 9999 &
+ (lon - dim > lonrange | lon + dim < -lonrange |
+ lat - dim > latrange | lat + dim < -latrange) then
+ next
+
+ a := [xscale * lon, yscale * lat]
+ while (dlon := move(4) - 5000) & (dlat := move(4) - 5000) do
+ put(a, xscale * (lon +:= dlon), yscale * (lat +:= dlat))
+
+# if beyond valid X range (with dx/dy margin), use library clipper
+ if (!a > 32000) | (!a < -32000) then
+ drawProc := DrawClipped
+ else
+ drawProc := DrawLine
+
+ push(a, w) # add graphics context
+
+ if find(\litName, map(\fename)) then {
+ put(litFeature, drawProc, a)
+ }
+
+ if /hilightOnly then {
+ if any('57', bdy) & (a[1] := \wintbl["Y" || bdy]) then {
+ drawProc ! a # draw boundary indicator
+ if any('F', class) then
+ next # chain is ONLY a boundary
+ a[1] := w
+ }
+ drawProc ! a # draw line itself
+ }
+ }
+ }
+ if w := \wintbl["LL"] then {
+ repeat {
+ drawProc := get(litFeature) | break
+ a := get(litFeature) | break
+ a[1] := w # replace graphics context
+ drawProc ! a
+ }
+ }
+
+ WAttrib("pointer=cross" | "pointer=crosshair")
+ return
+end
+
+
+
+procedure identify(x, y)
+ local line, lon, lat, dlon, dlat, dim, s, f
+ local fename, cfcc, bndry, x0, y0, w, h
+ local features
+
+ WAttrib("pointer=wait" | "pointer=watch")
+
+ # calculate region of interest in lat/lon coordinates
+ x := (x - EPSILON) / xscale
+ y := (y - EPSILON) / yscale
+ w := (1 + 2 * EPSILON) / xscale
+ h := (1 + 2 * EPSILON) / yscale
+
+ features := set()
+ every f := !ifileList do {
+ seek(f, 1)
+ read(f) # skip minima line
+ read(f) # skip maxima line
+
+ while line := read(f) do line ? {
+
+ if *Pending() > 0 then {
+ WAttrib("pointer=cross" | "pointer=crosshair")
+ return
+ }
+
+ cfcc := move(3)
+ bndry := move(1)
+ if ="|" then { # get feature name
+ fename := tab(upto('|'))
+ move(1)
+ }
+ else {
+ fename := ""
+ }
+ dim := integer(move(4))
+ lon := move(7) - ctrlon
+ lat := move(7) - ctrlat
+ if dim < 9999 &
+ (lon - dim > lonrange | lon + dim < -lonrange |
+ lat - dim > latrange | lat + dim < -latrange) then
+ next
+
+ x0 := lon
+ y0 := lat
+ while (dlon := move(4) - 5000) & (dlat := move(4) - 5000) do {
+ lon +:= dlon
+ lat +:= dlat
+ if ClipLine([x0, y0, lon, lat], x, y, w, h) then {
+ s := case bndry of {
+ "9":" (national boundary) "
+ "8":" (state boundary) "
+ "7":" (county boundary) "
+ "5":" (city limit) "
+ "0":" "
+ }
+ insert(features, cfcc || s || fename)
+ }
+ x0 := lon
+ y0 := lat
+ }
+ }
+ }
+ WAttrib("pointer=cross" | "pointer=crosshair")
+ Popup(, , , WAttrib("leading") * (0 ~= *features), popList, sort(features))
+ return
+end
+
+
+
+procedure popList(l)
+ WAttrib("row=1", "col=1")
+ every WWrite(!l)
+ until Active()
+end
+
+
+
+procedure settitle()
+ local lon, lat
+
+ lon := ctrlon * (360.0 / 9999999)
+ if lon > 180.0 then
+ lon -:= 360.0
+ lat := 90.0 - ctrlat * (180.0 / 9999999)
+ title := fnameList[1]
+ if *fnameList > 1 then
+ title ||:= "..."
+ title ||:= ": " || dms(lon, "W", "E") || " " || dms(lat, "S", "N")
+ WAttrib("label=" || title)
+ return
+end
+
+
+
+procedure dms(n, s1, s2)
+ local deg, min, sec
+
+ if n < 0 then
+ n := -n
+ else
+ s1 := s2
+
+ deg := integer(n)
+ n := (n - deg) * 60
+ min := integer(n)
+ n := (n - min) * 60
+ sec := integer(n + 0.5)
+
+ return deg || "\260" || right(min, 2, "0") || "'" ||
+ right(sec, 2, "0") || "\"" || s1
+end
+
+
+
+procedure setregion(lomin, lomax, ltmin, ltmax)
+ local xzoom, yzoom
+
+ setcenter((lomin + lomax + 1) / 2, (ltmin + ltmax + 1) / 2)
+ xzoom := ((dx - MARGIN) * 2 / fullx) / (lomax - lomin)
+ yzoom := ((dy - MARGIN) * 2 / fully) / (ltmax - ltmin)
+ if xzoom < yzoom then
+ setzoom(xzoom)
+ else
+ setzoom(yzoom)
+ return
+end
+
+
+
+# setclasses() -- initialize table of classifications
+#
+# The order used here is reflected in the Layers dialog box.
+
+procedure setclasses()
+ local c, w, mcolors, m
+
+ clist := [ # classification list
+ # prefix, psgray&w, label, color, width
+ class("A1", .0, 4, "roads", "black", 3), # freeway/tollway
+ class("A2", .0, 2, "roads", "black", 2), # primary road
+ class("A3", .0, 1, "roads", "black"), # secondary road
+ class("A4", .0, 0, "roads", "white"), # local road
+ class("A", .0, 0, "roads", "white"), # other road
+ class("B1", .4, 2, "railroads", "deep green", 2), # railroad line
+ class("B", .4, 1, "railroads", "deep green"), # r.r. spur, yard, etc.
+ class("H", .7, 1, "water", "dark cyanish blue"), # water
+ class("Y7", .9, 5, "major boundaries", "orange", 3), # county
+ class("Y5", .9, 3, "major boundaries", "orange", 2), # city
+ class("E", .9, 1, "minor boundaries", "light orange"), # visible
+ class("F", .9, 1, "minor boundaries", "light orange"), # invisible
+ class("D", .0, 1, "landmarks", "dark red"), # landmark
+ class("C", .5, 1, "piplines & power", "purple"), # pipe, power
+ class("LL", .2, 2, "highlighted feature", "yellow", 10), # hilit feature
+ class("T0", .8, 3, "GPS track", "dark greenish cyan", 2), # Track data
+ class("X", .8, 1, "unclassified", "purple")] # unclassified
+
+ every c := !clist do
+ c.vispfx := c.prefix # initially, all layers visible
+
+ ctable := table()
+ every c := !clist do
+ if *c.prefix = 1 then
+ every /ctable[c.prefix || !"0123456789"] := c
+ else
+ ctable[c.prefix] := c
+
+ wintbl := table() # global window table
+ mcolors := table() # local table of mutable colors
+
+ every c := !clist do {
+
+ w := Clone() | stop("can't clone window for ", c.label)
+ /mcolors[c.color] := NewColor(w, c.color) # may fail
+ c.index := mcolors[c.color]
+ Fg(w, \c.index | c.color) | stop("can't set color for ", c.label)
+
+ WAttrib(w, "linewidth=" || \c.width)
+ wintbl[c.prefix] := w
+ if *c.prefix = 1 then
+ every /wintbl[c.prefix || (0 to 9)] := w
+ }
+ return
+end
+
+
+
+# setlayers() -- bring up layers dialog
+
+procedure setlayers()
+ local c, i, defaults, buttons, choice, lset
+ static labels, values
+
+ initial {
+ lset := set()
+ labels := list()
+ values := list()
+ every c := !clist do
+ if \c.index & not member(lset, c.label) then {
+ insert(lset, c.label)
+ put(labels, c.label)
+ put(values, 1)
+ }
+ }
+
+ if *labels = 0 then {
+ Notice("No layer control available")
+ fail
+ }
+
+ while choice ~=== "Okay" do { # loop when "Apply" selected
+
+ defaults := values
+ buttons := ["Okay", "Apply", "Cancel"]
+ choice := ToggleDialog(msgwin, "Layers:", labels, defaults, buttons)
+ if choice == "Cancel" then
+ fail
+ values := dialog_value
+
+ # change mutable color for every item that changed in the dialog
+ every i := 1 to *values do
+ if values[i] ~=== defaults[i] then
+ every c := !clist do
+ if c.label == labels[i] then {
+ if \values[i] then {
+ Color(\c.index, c.color)
+ c.vispfx := c.prefix
+ }
+ else {
+ Color(\c.index, Bg())
+ c.vispfx := &null
+ }
+ }
+ }
+ return
+end
+
+
+
+procedure writelch(ofile)
+ local line, dim, lon, lat, f, a, b, x, y, dlon, dlat, nlon, nlat, w, head
+ local startlon, startlat, minlon, minlat, maxlon, maxlat, deltas, class
+
+ write(ofile, " ",
+ right(ctrlon - lonrange, 7), right(ctrlat - latrange, 7))
+ write(ofile, " ",
+ right(ctrlon + lonrange, 7), right(ctrlat + latrange, 7))
+
+ every f := !ifileList do {
+ seek(f, 1)
+ read(f) # skip minima line
+ read(f) # skip maxima line
+
+ while line := read(f) do line ? {
+ w := \wintbl[class := move(2)] | next
+ head := class || move(2)
+ if ="|" then {
+ head ||:= "|" || tab(upto('|') + 1)
+ }
+ dim := integer(move(4))
+ lon := move(7) - ctrlon
+ lat := move(7) - ctrlat
+# quick clip
+ if dim < 9999 &
+ (lon - dim > lonrange | lon + dim < -lonrange |
+ lat - dim > latrange | lat + dim < -latrange) then
+ next
+
+ a := [xscale * lon, yscale * lat]
+ while (dlon := move(4) - 5000) & (dlat := move(4) - 5000) do
+ put(a, xscale * (lon +:= dlon), yscale * (lat +:= dlat))
+ a := Coalesce(ClipLine(w, a)) | next
+ every b := !a do {
+ deltas := ""
+ startlon := minlon := maxlon := lon :=
+ round(get(b) / xscale) + ctrlon
+ startlat := minlat := maxlat := lat :=
+ round(get(b) / yscale) + ctrlat
+ while nlon := round(get(b) / xscale) + ctrlon do {
+ nlat := round(get(b) / yscale) + ctrlat
+ deltas ||:= right(nlon - lon + 5000, 4, "0")
+ deltas ||:= right(nlat - lat + 5000, 4, "0")
+ lon := nlon
+ lat := nlat
+ maxlon <:= lon
+ minlon >:= lon
+ maxlat <:= lat
+ minlat >:= lat
+ }
+ dim := startlon - minlon
+ dim <:= maxlon - startlon
+ dim <:= startlat - minlat
+ dim <:= maxlat - startlat
+ dim >:= 9999
+
+ write(ofile, head, right(dim, 4), right(startlon, 7, "0"),
+ right(startlat, 7, "0"), deltas)
+ }
+ }
+ }
+ return
+end
+
+# writeppm(ofile) -- write PPM image to ofile
+#
+# comments note latitude and longitude bounds in arc-seconds
+
+procedure writeppm(ofile)
+ local w, h, rw, rh, s, lon, lat, dlon, dlat
+
+ w := WAttrib("width")
+ h := WAttrib("height")
+ rw := real(w)
+ rh := real(h)
+
+ lon := ctrlon * (360.0 / 9999999)
+ if lon > 180.0 then
+ lon -:= 360.0
+ lat := 90.0 - ctrlat * (180.0 / 9999999)
+
+ dlon := lonrange * (360.0 / 9999999)
+ dlat := latrange * (180.0 / 9999999)
+
+ write(ofile, "P6")
+ write(ofile, "#RTIN")
+ write(ofile, "#lon,lat:",
+ arcs(lon - dlon), arcs(lat - dlat), arcs(lon - dlon), arcs(lat + dlat),
+ arcs(lon + dlon), arcs(lat + dlat), arcs(lon + dlon), arcs(lat - dlat))
+ write(ofile, "#x,y: 0.0 0.0 0.0 ", rh, " ", rw, " ", rh, " ", rw, " 0.0")
+ write(ofile, w, " ", h)
+ write(ofile, 255)
+
+ every writes(ofile, rgb24(Pixel(tmpwin)))
+
+ return
+end
+
+
+
+# arcs(n) -- format latitude or longitude in arc-seconds with leading space
+
+procedure arcs(n)
+ return " " || (n * 3600.0)
+end
+
+
+
+# rgb24(k) -- return 24-bit (3-byte) r-g-b value for k
+
+procedure rgb24(k)
+ local s, r, g, b
+ static t
+ initial t := table()
+
+ if s := \t[k] then
+ return s
+
+ (ColorValue(k | Color(k)) | fail) ? {
+ s := char(tab(upto(',')) / 256)
+ move(1)
+ s ||:= char(tab(upto(',')) / 256)
+ move(1)
+ s ||:= char(tab(0) / 256)
+ }
+ t[k] := s
+ return s
+end
+
+
+
+# writeps(ofile) -- write Encapsulated PostScript to ofile
+
+procedure writeps(ofile)
+ local x1, x2, y1, y2, line, prevcode, code, dim, lon, lat, a, n, c, f
+
+ x1 := integer((lonmin - ctrlon) * xscale - 0.5) - CLIP
+ y1 := integer((latmin - ctrlat) * yscale - 0.5) - CLIP
+ x2 := integer((lonmax - ctrlon) * xscale + 0.5) + CLIP
+ y2 := integer((latmax - ctrlat) * yscale + 0.5) + CLIP
+ x1 <:= -dx
+ y1 <:= -dy
+ x2 >:= dx
+ y2 >:= dy
+ epsheader(ofile, (dx + x1) * PSSCALE, (dy - y2) * PSSCALE,
+ (x2 - x1) * PSSCALE, (y2 - y1) * PSSCALE, "r")
+
+ every write(ofile, ![
+ "/m { moveto } bind def",
+ "/r { rlineto } bind def",
+ "/s { stroke } bind def",
+ "/w { .00666667 mul inch setlinewidth setgray stroke } bind def"])
+
+ every c := !clist do
+ write(ofile, "/", left(\c.vispfx, 2),
+ " { ", c.psgray, " ", c.pswidth, " w } bind def")
+
+ every f := !ifileList do {
+ seek(f, 1)
+ read(f) # skip minima line
+ read(f) # skip maxima line
+
+ while line := read(f) do line ? {
+ code := \ctable[move(2)].vispfx | next
+ move(2)
+# skip feature name
+ if ="|" then
+ tab(upto('|') + 1)
+ dim := integer(move(4))
+ lon := xscale * (move(7) - ctrlon)
+ lat := yscale * (move(7) - ctrlat)
+ if dim < 9999 &
+ (lon - dim > lonrange | lon + dim < -lonrange |
+ lat - dim > latrange | lat + dim < -latrange) then
+ next
+ else {
+ writes(ofile, integer(PSSCALE * (dx + lon)), " ",
+ integer(PSSCALE * (dy - lat)), " m")
+ while lon := move(4) - 5000 & lat := move(4) - 5000 do
+ writes(ofile, "\n", integer(PSSCALE * xscale * lon), " ",
+ integer(-PSSCALE * yscale * lat), " r")
+ write(ofile, " ", (prevcode ~===:= code) | "s")
+ }
+ }
+ }
+ write(ofile, "showpage")
+ return
+end