#!/usr/bin/wish -f ## ## Tk SCRIPT NAME: tkMakePNGmapFromOSMtiles_aroundLatLon_OneZoomLevel_NxMtiles.tk ## ##+####################################################################### ## PURPOSE: This Tk script provides a GUI for entering (or selecting) a ## latitude-longitude location on the Earth (in decimal degrees). ## ## In addition, the GUI also allows the user to select an OSM zoom-level ## --- where an OSM 'zoom-level' indicator is an integer between 0 and 19. ## (OSM = Open Street Map) ## ## The GUI also allows the user to set integers M1,M2,N1,N2 ## that specify how many tiles on the left,right,top,bottom ## around a 'central' tile (determined from the ## user-specified latitude and longitude) are to be retrieved ## from an OSM PNG-file server. ## ## These 4 integers are used to specify that M1+1+M2=M columns and ## N1+1+N2=N rows of tiles (256x256 pixel PNG files) ## are to be joined to make a single PNG file --- ## which provides a map of the specified region. ## ## Reference: ## http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels ## ## Once the user specified on the GUI ## - latitude and longitude (in decimal degrees) ## - zoom-level (an integer between 0 and 18 or 19) ## - integers M1,M2,N1,N2 ## then this script performs the following steps: ## ## 1) converts that latitude-longitude (in decimal degrees) ## into a pair of integers M0,N0 that (for the specified ## zoom-level, 'z') determines an OSM tile --- a URL that ## specifies a 256x256 PNG filename, example: ## http://tile.openstreetmap.org/z/M0/N0.png ## ## 2) uses the 'wget' command to fetch a total of ## NxM PNG files --- where there will be N1+1+N2=N rows of tiles ## and M1+1+M2=M columns of tiles. These NxM tile files are put ## into a local directory, such as /tmp. ## ## 3) concatenates these PNG files into rows of tiles ## row N0 - N1 : concatenate M0 - M1, ... , M0 , ... , M0 + M1 ## ... ## row N0 : ditto ## ... ## row N0 + N2 : ditto ## ## Then concatenates the row images into the final image ## composed of NxM tiles. ## ## (The ImageMagick 'convert' command can be used to perform ## the concatenations --- multiple files in ONE horizontal concatenation ## and multiple files in ONE vertical concatenation. In other words, ## two calls to 'convert' can build the resulting map PNG file.) ## ## 4) The resulting image of NxM joined tiles is shown in an ## image viewer --- an image viewer that the user can specify ## in a variable 'IMGviewer' set at the bottom of this script. ## ## Examples: set IMGviewer "/usr/bin/eog" ## or ## set IMGviewer "/usr/bin/eom" ## ## to specify the EyeOfGnome or EyeOfMATE ## image viewer on Linux. ## ## 5) The user can close the image viewer window and ## then specify another Earth location and/or ## zoom level and integers M1,M2,N1,N2 and do another ## fetch-and-concatenate-and-show. ## ####################### ## ANOTHER TK SCRIPT TO HELP DETERMINE AN APPROPRIATE ZOOM-LEVEL: ## ## To get an idea of the zoom-level to use, there is a separate ## Tk GUI ('tkGooie') utility that allows the user to easily ## sample some OSM tiles (256x256 PNG files) at a specific ## Earth location for several zoom levels. ## ## This 'external' Tk GUI multiple-zoom-levels script can be called ## by this GUI --- say via a 'CheckZoomLevels' button on the GUI --- ## to allow the user to select a site on the Earth (city, state, country, ## national park, etc.). The external script provides the ## latitude,longitude location of the user-selected site. ## ##+############################ ## NOTES ON OTHER IMAGE VIEWERS: ## Other 'simple', quick-starting image viewer programs that ## might be useful are 'gwenview', 'gpicview', 'feh', 'ffplay', ## ImageMagick 'display', and quite a few others. ## ##+################# ## THE GUI WIDGETS: ## ## The options available to the user are compactly indicated ## by the following 'sketch' of the GUI: ## ## FRAME ## NAMES ## VVVVVVVVVV ## ------------------------------------------------------------------------------------ ## Make a PNG-file MAP - from OSM Tiles (256x256 PNG files) - for a Latitude-Longitude & OSM Zoom-Level ## [window title] ## ------------------------------------------------------------------------------------ ## ## .fRbuttons {Exit} {Help} {GetLatLon} {MakePNGfileMAP} ZoomLevel (0 to 18): -__+ ## ## .fRlatlon Central Tile Location in decimal degrees - Latitude: ___________ Longitude: __________ ## ## .fRcols Number of Columns of tiles - To Left: -__+ To Right: -__+ of a central tile. ## ## .fRrows Number of Rows of tiles - Above: -___+ Below: -__+ a central tile. ## ## .fRmsg [---------- a message line goes here in a label widget -------------------------] ## ## -------------------------------------------------------- ## ## In the above sketch of the GUI: ## ## Square brackets indicate a comment (not to be placed on the GUI). ## Braces indicate a Tk 'button' widget. ## Underscores indicate a Tk 'entry' widget. ## A colon indicates that the text before the colon is on a 'label' widget. ## Capital-O indicates a Tk 'radiobutton' widget, if any. ## Capital-X indicates a Tk 'checkbutton' widget, if any. ## ##+############## ## GUI components: ## ## From the GUI 'sketch' above, it is seen that the GUI consists of ## about ## ## - 4 LARGE button widgets -- plus 10 SMALL minus and plus buttons on either side of ## five small entry fields for integers (2 digits each) ## - 10 label widgets ## - 7 entry widgets ## - 0 radiobutton widgets ## - 0 checkbutton widgets ## - 0 scale widgets ## - 0 listbox widgets ## - 0 canvas widgets ## - 0 text widgets ## ##+##################################################################### ## CALLED BY: This script is intended to be called from a 'MAPtools' ## toolchest of the FE 'tkGooies' menus/toolchests system. ##+######################################################################## ## STRUCTURE OF THIS CODE: ## ## 0) Set general window parms (win-name, win-position, win-color-scheme, ## fonts, widget-geom-parms, win-size-control, text-array-for-labels-etc). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack the frames. ## ## 2) Define & pack all widgets in the frames, frame by frame. ## After all the widgets for a frame are defined, pack them in the frame. ## ## 3) Define keyboard and/or mouse/touchpad/touch-sensitive-screen 'event' ## BINDINGS, if needed. ## 4) Define PROCS, if needed. ## 5) Additional GUI INITIALIZATION (typically with one or more of ## the procs), if needed. ## ## In more detail: ## ## 1a) Define ALL frames -- and sub-frames: ## ## Top-level : ## 'fRbuttons' for Exit, Help, ... buttons and a zoom-level entry field ## 'fRlatlon' for latitude-longitude entry fields ## 'fRcols' for 2 entry fields ## 'fRrows' for 2 entry fields ## 'fRmsg' for messages to the user ## ## 1b) Pack ALL frames, including sub-frames (if any). ## ## 2) Define & pack all widgets in the frames -- basically going through ## frames & their interiors in left-to-right, top-to-bottom order: ## ## 3) Define bindings: See the BINDINGS section below. ## ## 4) Define procs: See the PROCS section below. ## ## 5) Additional GUI initialization: See this section at the bottom ## of this script. ##+####################################################################### ## DEVELOPED WITH: Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala') ## ## $ wish ## % puts "$tcl_version $tk_version" ## ## showed ## 8.5 8.5 ## but this script should work in most previous 8.x versions, and probably ## even in some 7.x versions (if font handling is made 'old-style'). ##+######################################################################## ## MAINTENANCE HISTORY: ## Started by: Blaise Montandon 2016oct30 Started development, on Ubuntu 9.10, ## based on the code of some Tk scripts ## of mine that contained most of the ## widgets needed. ## Updated by: Blaise Montandon 2016 ##+####################################################################### ##+###################################################### ## Set WINDOW TITLE and POSITION. ##+###################################################### wm title . "Make a PNG-file MAP - from OSM Tiles (256x256 PNG files) - for a Latitude-Longitude & OSM Zoom-Level" wm iconname . "MakeMap" wm geometry . +15+30 # wm geometry . +250+285 ##+###################################################### ## Set the COLOR SCHEME for the window and its widgets --- ## such as listbox and entry field background color. ##+###################################################### tk_setPalette "#e0e0e0" set entryBKGD "#ffffff" # set radbuttBKGD "#ffffff" # set chkbuttBKGD "#ffffff" # set scaleBKGD "#f0f0f0" # set listboxBKGD "#f0f0f0" # set textBKGD "#f0f0f0" ##+######################################################## ## DEFINE (temporary) FONT NAMES. ## ## We use a VARIABLE-WIDTH font for text on LABEL and ## BUTTON widgets. ## ## We use a FIXED-WIDTH font for LISTBOX lists, ## for text in ENTRY fields --- and often for text in ## TEXT widgets. ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -weight bold \ -slant roman ## Some other possible (similar) variable width fonts: ## Arial ## Bitstream Vera Sans ## DejaVu Sans ## Droid Sans ## FreeSans ## Liberation Sans ## Nimbus Sans L ## Trebuchet MS ## Verdana font create fontTEMP_fixedwidth \ -family {liberation mono} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -weight bold \ -slant roman ## Some other possible fixed width fonts (esp. on Linux): ## Andale Mono ## Bitstream Vera Sans Mono ## Courier 10 Pitch ## DejaVu Sans Mono ## Droid Sans Mono ## FreeMono ## Nimbus Mono L ## TlwgMono ##+########################################################### ## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS. ## (e.g. width and height of canvas, and padding for Buttons) ##+########################################################### ## LABEL widget geom settings: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 ## BUTTON widget geom settings: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 ## ENTRY widget geom settings: set BDwidthPx_entry 2 ## COMMENT some widget geom parms. if {0} { ## RADIOBUTTON widget geom settings: set PADXpx_radbutton 0 set PADYpx_radbutton 0 set BDwidthPx_radbutt 2 ## CHECKBUTTON widget geom settings: set PADXpx_chkbutton 0 set PADYpx_chkbutton 0 set BDwidthPx_chkbutton 2 ## TEXT widget geom settings: set BDwidthPx_text 2 ## SCALE widget geom parameters: # set BDwidthPx_scale 2 # set scaleThicknessPx 10 } ## END of commented geom parms ##+############################################################## ## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI. ## NOTE: This can aid INTERNATIONALIZATION. This array can ## be set according to a nation/region parameter. ##+############################################################## ## if { "$VARlocale" == "en"} ## For '.fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(buttonGETLATLON) "Sites-LatLon" set aRtext(buttonRESETPARMS) "ResetEntries" set aRtext(buttonMAKEMAP) "MakePNGfileMAP" set aRtext(labelZOOMLEVEL) " ZoomLevel (0 to 18):" set aRtext(buttZLEVELDECR) "-" set aRtext(buttZLEVELINCR) "+" ## For '.fRlatlon' frame: set aRtext(labelLATITUDE) "CENTRAL TILE LOCATION in decimal degrees - Latitude" set aRtext(labelLONGITUDE) "Longitude:" ## For '.fRcols' frame: set aRtext(labelCOLSleft) "Number of COLUMNS of tiles - To LEFT:" set aRtext(buttCOLSLEFTDECR) "-" set aRtext(buttCOLSLEFTINCR) "+" set aRtext(labelCOLSright) " To RIGHT:" set aRtext(buttCOLSRIGHTDECR) "-" set aRtext(buttCOLSRIGHTINCR) "+" set aRtext(labelCOLSmodifier) "of a 'central' tile." ## For '.fRrows' frame: set aRtext(labelROWSvert1) "Number of ROWS of tiles - BELOW:" set aRtext(buttROWSVERT1DECR) "-" set aRtext(buttROWSVERT1INCR) "+" set aRtext(labelROWSvert2) " ABOVE:" set aRtext(buttROWSVERT2DECR) "-" set aRtext(buttROWSVERT2INCR) "+" set aRtext(labelROWSmodifier) "a 'central' tile." ## For some messages: set aRtext(msgHOWtoMAKE) \ "** Supply a zoom-level and a latitude-longitude (you can use the '$aRtext(buttonGETLATLON)' button), ** and choose a desired number of tiles to left,right,above,below a central tile. ** Then click '$aRtext(buttonMAKEMAP)' to build and show a map composed of concatenated ** tiles for the specified zoom-level and the specified ('central') latitude-longitude." ## END OF if { "$VARlocale" == "en"} ##+###################################################################### ## Set a MIN-SIZE of the window (roughly). ## ## For WIDTH, allow for the min-width of the '.fRbuttons' frame. ## ## For HEIGHT, allow for the stacked frames: ## 1 char high for the '.fRbuttons' frame ## 1 char high for the '.fRlatlon' frame ## 1 char high for the '.fRcols' frame ## 1 char high for the '.fRrows' frame ## 1 char high for the '.fRmsg' frame ## -------- ## 5 chars high for the 5 frames ##+##################################################################### ## FOR WIDTH: (allow for widgets in the '.fRbuttons' frame) set minWidthPx [font measure fontTEMP_varwidth \ " $aRtext(buttonEXIT) $aRtext(buttonHELP) $aRtext(buttonGETLATLON) \ $aRtext(buttonMAKEMAP) $aRtext(labelZOOMLEVEL)"] ## We add some pixels to account for right-left-size of ## window-manager decoration (~8 pixels) and some pixels for ## frame/widget borders (~5 widgets x 4 pixels/widget = 20 pixels). set minWinWidthPx [expr {28 + $minWidthPx}] ## For HEIGHT --- for ## 1 char high for the '.fRbuttons' frame ## 1 char high for the '.fRlatlon' frame ## 1 char high for the '.fRcols' frame ## 1 char high for the '.fRrows' frame ## 1 char high for the '.fRmsg' frame ## -------- ## 5 chars high for the 5 frames set charHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {5 * $charHeightPx}] ## Add about 20 pixels for top-and-bottom window decoration -- ## and some pixels for top-and-bottom of frame/widget borders ## (~5 widgets x 4 pixels/widget = 20 pixels). set minWinHeightPx [expr {40 + $minWinHeightPx}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We may allow the window to be resizable. We pack the canvases ## (and the frames that contain them) with '-fill both -expand 1' ## so that the canvases can be enlarged by enlarging the window. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ## We fix the y-size of the window, but allow the x-size to vary. wm resizable . 1 0 ##+#################################################################### ##+#################################################################### ## DEFINE *ALL* THE FRAMES: ## ## Top-level : ## 'fRbuttons' for Exit, Help, ... buttons ## 'fRlatlon' for latitude,longitude entry fields ## 'fRcols' for 2 entry fields ## 'fRrows' for 2 entry fields ## 'fRmsg' for messages to the user ##+#################################################################### ##+#################################################################### ## FOR TESTING change 0 to 1: ## (Example1: To see appearance of frames when borders are drawn.) ## (Example2: To see sizes of frames for various '-fill' options.) ## (Example3: To see how frames expand as window is resized.) if {0} { set RELIEF_frame raised set BDwidthPx_frame 2 } else { set RELIEF_frame flat set BDwidthPx_frame 0 } frame .fRbuttons -relief $RELIEF_frame -bd $BDwidthPx_frame frame .fRlatlon -relief raised -bd 2 frame .fRcols -relief raised -bd 2 frame .fRrows -relief raised -bd 2 frame .fRmsg -relief raised -bd 2 ##+######################################################## ## PACK *ALL* the FRAMES. ##+######################################################## pack .fRbuttons \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRlatlon \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcols \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRrows \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRmsg \ -side top \ -anchor nw \ -fill x \ -expand 0 ##+################################################################ ##+################################################################ ## START DEFINING & PACKING WIDGETS WITHIN THEIR FRAMES. ##+################################################################ ##+################################################################ ##+######################################################## ## IN THE '.fRbuttons' frame -- DEFINE several buttons ## --- Exit, Help, .... Then PACK them. ##+######################################################## button .fRbuttons.buttEXIT \ -text "$aRtext(buttonEXIT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {exit} button .fRbuttons.buttHELP \ -text "$aRtext(buttonHELP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {popup_msgVarWithScroll .topHelp "$HELPtext" $POPUPloc} button .fRbuttons.buttGETLATLON \ -text "$aRtext(buttonGETLATLON)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {get_lat_lon} button .fRbuttons.buttMAKEMAP \ -text "$aRtext(buttonMAKEMAP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {make_PNG_map} button .fRbuttons.buttRESETPARMS \ -text "$aRtext(buttonRESETPARMS)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {reset_parms} label .fRbuttons.labelZOOMLEVEL \ -text "$aRtext(labelZOOMLEVEL)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label button .fRbuttons.buttZLEVELDECR \ -text "$aRtext(buttZLEVELDECR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {zlevel_decrement} entry .fRbuttons.entryZOOMLEVELS \ -textvariable ENTRYzoomLevel \ -width 2 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry button .fRbuttons.buttZLEVELINCR \ -text "$aRtext(buttZLEVELINCR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {zlevel_increment} ##+######################################## ## Pack the widgets in the 'fRbuttons' frame ##+######################################## pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttGETLATLON \ .fRbuttons.buttMAKEMAP \ .fRbuttons.buttRESETPARMS \ .fRbuttons.labelZOOMLEVEL \ .fRbuttons.buttZLEVELDECR \ .fRbuttons.entryZOOMLEVELS \ .fRbuttons.buttZLEVELINCR \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## IN THE '.fRlatlon' frame -- DEFINE a pair of ## LABEL and ENTRY widgets. Then PACK them. ##+######################################################## label .fRlatlon.labelLATITUDE \ -text "$aRtext(labelLATITUDE)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label entry .fRlatlon.entryLATITUDE \ -textvariable ENTRYlatitude \ -width 10 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry ## We set an initial value for the latitude entry var ## in the 'Additional GUI Initialization' section at ## the bottom of this script. label .fRlatlon.labelLONGITUDE \ -text "$aRtext(labelLONGITUDE)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label entry .fRlatlon.entryLONGITUDE \ -textvariable ENTRYlongitude \ -width 10 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry ## We set an initial value for the longitude entry var ## in the 'Additional GUI Initialization' section at ## the bottom of this script. ##+######################################## ## PACK the widgets in the 'fRlatlon' frame. ##+######################################## pack .fRlatlon.labelLATITUDE \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRlatlon.entryLATITUDE \ -side left \ -anchor w \ -fill x \ -expand 1 pack .fRlatlon.labelLONGITUDE \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRlatlon.entryLONGITUDE \ -side left \ -anchor w \ -fill x \ -expand 1 ##+######################################################## ## IN THE '.fRcols' frame -- DEFINE a pair of ## LABEL and ENTRY widgets, and another LABEL widget. ## Then PACK them. ##+######################################################## label .fRcols.labelCOLSleft \ -text "$aRtext(labelCOLSleft)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label button .fRcols.buttCOLSLEFTDECR \ -text "$aRtext(buttCOLSLEFTDECR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {colsleft_decrement} entry .fRcols.entryCOLSleft \ -textvariable ENTRYcolsLeft \ -width 2 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry button .fRcols.buttCOLSLEFTINCR \ -text "$aRtext(buttCOLSLEFTINCR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {colsleft_increment} ## We set an initial value for the cols-left entry var ## in the 'Additional GUI Initialization' section at ## the bottom of this script. label .fRcols.labelCOLSright \ -text "$aRtext(labelCOLSright)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label button .fRcols.buttCOLSRIGHTDECR \ -text "$aRtext(buttCOLSRIGHTDECR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {colsright_decrement} entry .fRcols.entryCOLSright \ -textvariable ENTRYcolsRight \ -width 2 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry button .fRcols.buttCOLSRIGHTINCR \ -text "$aRtext(buttCOLSRIGHTINCR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {colsright_increment} ## We set an initial value for the cols-right entry var ## in the 'Additional GUI Initialization' section at ## the bottom of this script. label .fRcols.labelCOLSmodifier \ -text "$aRtext(labelCOLSmodifier)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label ##+######################################## ## PACK the widgets in the 'fRcols' frame. ##+######################################## pack .fRcols.labelCOLSleft \ .fRcols.buttCOLSLEFTDECR \ .fRcols.entryCOLSleft \ .fRcols.buttCOLSLEFTINCR \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRcols.labelCOLSright \ .fRcols.buttCOLSRIGHTDECR \ .fRcols.entryCOLSright \ .fRcols.buttCOLSRIGHTINCR \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRcols.labelCOLSmodifier \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## IN THE '.fRrows' frame -- DEFINE a pair of ## LABEL and ENTRY widgets, and another LABEL widget. ## Then PACK them. ##+######################################################## label .fRrows.labelROWSvert1 \ -text "$aRtext(labelROWSvert1)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label button .fRrows.buttROWSVERT1DECR \ -text "$aRtext(buttROWSVERT1DECR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {rowsvert1_decrement} entry .fRrows.entryROWSvert1 \ -textvariable ENTRYrowsVert1 \ -width 2 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry button .fRrows.buttROWSVERT1INCR \ -text "$aRtext(buttROWSVERT1INCR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {rowsvert1_increment} ## We set an initial value for the rows-above entry var ## in the 'Additional GUI Initialization' section at ## the bottom of this script. label .fRrows.labelROWSvert2 \ -text "$aRtext(labelROWSvert2)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label button .fRrows.buttROWSVERT2DECR \ -text "$aRtext(buttROWSVERT2DECR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {rowsvert2_decrement} entry .fRrows.entryROWSvert2 \ -textvariable ENTRYrowsVert2 \ -width 2 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry button .fRrows.buttROWSVERT2INCR \ -text "$aRtext(buttROWSVERT2INCR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {rowsvert2_increment} ## We set an initial value for the rows-below entry var ## in the 'Additional GUI Initialization' section at ## the bottom of this script. label .fRrows.labelROWSmodifier \ -text "$aRtext(labelROWSmodifier)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label ##+######################################## ## PACK the widgets in the 'fRrows' frame. ##+######################################## pack .fRrows.labelROWSvert1 \ .fRrows.buttROWSVERT1DECR \ .fRrows.entryROWSvert1 \ .fRrows.buttROWSVERT1INCR \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRrows.labelROWSvert2 \ .fRrows.buttROWSVERT2DECR \ .fRrows.entryROWSvert2 \ .fRrows.buttROWSVERT2INCR \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRrows.labelROWSmodifier \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## IN THE '.fRmsg' frame -- DEFINE a LABEL widget. ## Then PACK it. ##+######################################################## label .fRmsg.labelMSG \ -text "" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label \ -bg #ff6600 ## PACK the widget in the 'fRmsg' frame. pack .fRmsg.labelMSG \ -side left \ -anchor w \ -fill x \ -expand 1 ##+##################################################################### ## END OF SECTION TO DEFINE AND PACK THE GUI WIDGETS. ##+##################################################################### ##+##################################################################### ##+##################################################################### ## DEFINE BINDINGS: button1-release bindings on some widgets ??? ## Return-key bindings on some entry widgets ??? ##+##################################################################### # bind .fR?????.???widget??? {??proc??} # bind .fR?????.???widget??? {??proc??} ##+##################################################################### ##+##################################################################### ## DEFINE PROCEDURES: ## ## 'get_lat_lon' - called by the 'Sites-LatLon' button ## ## 'z_lat_lon_TO_col_row' - called by proc 'make_PNG_map' ## ## 'make_PNG_map' - called by the 'MakePNGfileMAP' button. ## ## 'advise_user' - called by the 'make_PNG_map' proc and in ## the 'Additional GUI Initialization' section ## at the bottom of the script. ## ## 'zlevel_increment' - called via the zoom-level '+' button ## 'zlevel_decrement' - called via the zoom-level '-' button ## ## 'colsleft_increment' - called via the cols-left '+' button ## 'colsleft_decrement' - called via the cols-left '-' button ## ## 'colsright_increment' - called via the cols-right '+' button ## 'colsright_decrement' - called via the cols-right '-' button ## ## 'rowsvert1_increment' - called via the rows-above '+' button ## 'rowsvert1_decrement' - called via the rows-above '-' button ## ## 'rowsvert2_increment' - called via the rows-below '+' button ## 'rowsvert2_decrement' - called via the rows-below '-' button ## ## 'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var. ##+##################################################################### ##+##################################################################### ##+##################################################################### ## PROC: 'get_lat_lon' ## ## PURPOSE: Call on an external Tk GUI script to allow the user ## to select a site (city, state, country, national park, other) ## and extract the latitude and longitude (in decimal degrees) ## from the string returned by that external script. ## ## CALLED BY: the 'GetLatLon' button. ##+##################################################################### proc get_lat_lon {} { global SELECTORlatlon ENTRYlatitude ENTRYlongitude ############################################# ## Get an 'image-playlist' file name. ############################################# set TEMPstring [exec "$SELECTORlatlon"] ## FOR TESTING: # puts "TEMPstring : $TEMPstring" ################################### ## Check if TEMPstring var is empty. ################################### if {"$TEMPstring" == ""} {return} ##################################################### ## Extract latitude and longitude from TEMPstring ## and put those numbers in the ENTRYlatitude and ## ENTRYlongitude variables. ##################################################### set TEMPlatlon [lindex [split "$TEMPstring" "#"] 1] set ENTRYlatitude [lindex "$TEMPlatlon" 0] set ENTRYlongitude [lindex "$TEMPlatlon" 1] } ## END OF PROC 'get_lat_lon' ##+################################################################### ## PROC: 'z_lat_lon_TO_col_row' ##+################################################################### ## PURPOSE: For a given zoom-level, latitude (in decimal degrees), ## and longitude (in decimal-degrees), ## return two integers for column(longitude),row(latitude) ## of an OSM tile for that zoom-level. ## ## REFERENCE: ## http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels ## ## CALLED BY: the 'make_PNG_map' proc ##+################################################################### proc z_lat_lon_TO_col_row {zoomlevel lat_deg lon_deg} { global pi radsPERdeg set lat_rad [expr {$radsPERdeg * $lat_deg}] set n [expr {pow(2,$zoomlevel)}] set col [expr {int( ($lon_deg + 180.0) / 360.0 * $n)}] set row [expr {int( (1.0 - log(tan($lat_rad) + (1 / cos($lat_rad))) / $pi) / 2.0 * $n)}] ## FOR TESTING: if {0} { puts "" puts "From PROC 'z_lat_lon_TO_col_row':" puts "INPUT : zoomlevel: $zoomlevel lat_deg: $lat_deg lon_deg: $lon_deg" puts "OUTPUT: col: $col row: $row" } return [list $col $row] } ## END OF PROC 'z_lat_lon_TO_col_row' ##+################################################################### ## PROC: 'make_PNG_map' ##+################################################################### ## PURPOSE: For a given zoom-level (integer) and ## a given latitude,longitude, and ## four integers (M1,M2,N1,N2), ## fetch a set of tiles from an OpenStreetMap server and ## concatenate the tiles into a single large PNG file ## making a single map image file. ## ## METHOD: Note that ## M = M1+1+M2 will be the number of rows of tiles in the final PNG ## and N = N1+1+N2 will be the number of columns of tiles in the final PNG. ## ## In an outer loop over m = M0-M1 to M0+M2 (a row index), and ## in an inner loop over n = N0-N1 to N0+N2 (a column index), ## ## for a given m (row), N1+1+N2 PNG files are retrieved ## from the OSM server using 'wget' --- ## to a local directory, like /tmp. In other words: ## ## Using 'wget', for each triple (z,row-m,col-n), we get the PNG file at ## http://tile.openstreetmap.org/z/col-n/row-m.png ## ## After the 'N0+N2' PNG file is retrieved, the row of PNG files ## are concatenated into a single (row) PNG file --- using ## ImageMagick 'convert'. ## ## At the end of this double-loop, we have row-images ## corresponding to rows M0-M1 to M0+M2. ## These row-image files are concatenated into a single ## large PNG file --- the final map --- using 'convert'. ## ## Then the image viewer, IMGviewer, is used to show the big PNG file. ## ## CALLED BY: the 'MakePNGfileMAP' button ##+################################################################## proc make_PNG_map { } { global ENTRYzoomLevel ENTRYlatitude ENTRYlongitude \ ENTRYcolsLeft ENTRYcolsRight ENTRYrowsVert1 ENTRYrowsVert2 \ WGETpgm DIRtemp TILEprefix ROWprefix CONVERTpgm \ IMGviewer POPUPloc aRtext env ##################################################### ## Get the OSM row and column of the 'central' tile. ##################################################### set TEMPcolrow [z_lat_lon_TO_col_row $ENTRYzoomLevel $ENTRYlatitude $ENTRYlongitude] set N0 [lindex $TEMPcolrow 0] set M0 [lindex $TEMPcolrow 1] ##################################################### ## Set MIN and MAX row and column indices. ##################################################### set MINrow [expr {$M0 - $ENTRYrowsVert2}] set MAXrow [expr {$M0 + $ENTRYrowsVert1}] set MINcol [expr {$N0 - $ENTRYcolsLeft}] set MAXcol [expr {$N0 + $ENTRYcolsRight}] ######################################################### ## Remove previous 'tile_*' and 'row_*' files in the ## temp directory --- so we do not take a lot of space with ## files from previous map-making runs in this bootup session. ######################################################### catch {exec /bin/sh -c "rm $DIRtemp/${TILEprefix}*"} catch {exec /bin/sh -c "rm $DIRtemp/${ROWprefix}*"} ######################################################### ## Advise user that this may take some time depending ## on the number of tiles being fetched and joined. ######################################################### set TEMProws [expr {($MAXrow - $MINrow) + 1}] set TEMPcols [expr {($MAXcol - $MINcol) + 1}] set TEMPtiles [expr {$TEMProws * $TEMPcols}] set TEMPtiles2 [expr {2 * $TEMPtiles}] advise_user \ "** FETCH-and-JOIN PROCESSING IS STARTING. $TEMPtiles tiles are being fetched-and-joined. ** This may take about 2 * $TEMPtiles = $TEMPtiles2 seconds. Wait ..." ##################################################### ## Start the double-loop to retrieve and assemble the ## PNG files from the OSM server. ##################################################### ############################################################### ## Initialize a variable in which to assemble a string of ## concatenated-into-a-row PNG filenames for the final mage. ############################################################### set ROWfiles "" ## OUTER-loop over row indices. for {set m $MINrow} {$m <= $MAXrow} {incr m} { ############################################################### ## Initialize a variable in which to assemble a string of ## PNG filenames for a row-image. ############################################################### set ROWtilenames "" ## INNER-loop over column indices. for {set n $MINcol} {$n <= $MAXcol} {incr n} { ###################################################################### ## Set the URL-name of the PNG to fetch, and ## set the fullname for the PNG file to be put in the local directory. ###################################################################### set URLname "http://tile.openstreetmap.org/${ENTRYzoomLevel}/$n/$m.png" set OUTfile "$DIRtemp/${TILEprefix}${ENTRYzoomLevel}_${n}_${m}.png" set ROWtilenames "$ROWtilenames $OUTfile" #################################################################### ## Some error-checking with 'catch' statements on these 'exec' ## statements may be needed. See 'tkGooie' script ## tk_showImgFilesInPlaylist_FrontEnd.tk #################################################################### ## SOME SYNTAX NOTES on running 'external' programs via a Tcl 'exec': #################################################################### ## On page 105 of the 4th edition of 'Practical Programming in Tcl & Tk', ## is the following quote on the Tcl 'exec' command: ## ## "The 'exec' command runs programs from your Tcl script. For example: ## set d [exec date] ## The standard output of the program is returned as the value of ## the 'exec' command. However, if the program writes to its standard ## error channel or exits with a nonzero status code, then 'exec' ## raises an error. If you do not care about the exit status, or you ## use a program that insists on writing to standard error, then you ## can use 'catch' to mask the errors: ## ## catch {exec program arg arg} result" ## ## This is an example of running a program in 'foreground' mode. ####################################################################### ## On page 107 of the 4th edition of 'Practical Programming in Tcl & Tk', ## is the following quote on the Tcl 'exec' command and 'background' mode: ## ## "A trailing '&' causes the program to run in the background. ## In this case, the process identifier is returned by the 'exec' ## command. Otherwise, the 'exec' command blocks during execution ## of the program, and the standard output of the program is the ## return code of 'exec'." ## ## Page 83 of the same book says: ## "'catch' returns zero if there was no error caught, ## or a nonzero error code if it did catch an error." #################################################################### ## A couple of examples of using a PID (process ID) with Tcl: ## ## catch {eval exec $feREADER_text \"$FULFILname\" &} ViewerPID ## ## set RETcode [ catch {eval exec ${feDIR}/tkGUIs/shofil.tk \ ## "$FULFILname" &} ViewerPID ] ## #################################################################### ## An alternative form of trying 'exec': ## exec /bin/sh -c "$...." #################################################################### ############################################################### ## A 'foreground' run of 'wget' per page 105 of the 4th edition ## of 'Practical Programming in Tcl & Tk'. ## ## (We do not want to run 'wget' in the background --- with & --- ## because we want to make sure we get each PNG file before ## we start concatenating them into a 'row' PNG file.) ## ## We MAY need to use 'eval' to avoid a 'not found' error if ## we append some parms (or a space) to the command. ############################################################### ## An example of the default message from a SUCCESSFUL 'wget' fetch: ## ## --2016-10-30 21:52:28-- http://tile.openstreetmap.org/4/7/6.png ## Resolving tile.openstreetmap.org... 140.211.167.105 ## Connecting to tile.openstreetmap.org|140.211.167.105|:80... connected. ## HTTP request sent, awaiting response... 200 OK ## Length: 12300 (12K) [image/png] ## Saving to: `/tmp/tile_4_7_6.png' ## ## 0K .......... .. 100% 103K=0.1s ## ## 2016-10-30 21:52:28 (103 KB/s) - `/tmp/tile_4_7_6.png' saved [12300/12300] ## ################################ ## A NON-VERBOSE (-nv) message: ## ## 2016-10-30 22:59:09 URL:http://tile.openstreetmap.org/16/29964/27434.png [103/103] -> "/tmp/tile_16_29964_27434.png" [1] ## ############################################################### ## An example of message from an UNSUCCESSFUL 'wget' fetch ## due to no internet connection: ## ## --2016-10-30 21:56:30-- http://tile.openstreetmap.org/4/7/6.png ## Resolving tile.openstreetmap.org... failed: Name or service not known. ## wget: unable to resolve host address `tile.openstreetmap.org' ## ################################ ## The NON-VERBOSE (-nv) message: ## ## wget: unable to resolve host address `tile.openstreetmap.org' ## ################################################################ ##################################################################### ## '-nv' is non-verbose --- not as quieting as '-q'. ##################################################################### set VARcommand "wget -nv -O $OUTfile $URLname" catch {eval exec $VARcommand} ForegroundResult ##################################################################### ## 'string match {wget: unable to resolve host address*} str' ## returns 1 if the pattern 'wget: unable to resolve host address*' ## is in the string str. ##################################################################### set MatchIndex [string match {wget: unable to resolve host address*} "$ForegroundResult"] ## FOR TESTING: if {0} { puts "" puts "From PROC 'make_PNG_map':" puts "ForegroundResult:" puts "$ForegroundResult" puts "MatchIndex: $MatchIndex" } if {$MatchIndex == 1} { set ERRtext \ "Proc 'make_PNG_map' seems to have encountered an error in trying to 'wget' URL-file $URLname Message: $ForegroundResult Stopping processing. MAKE SURE YOUR NETWORK CONNECTION IS 'UP'." popup_msgVarWithScroll .topErr "$ERRtext" $POPUPloc return } ############################################################### ## Pause about 500 millisecs to allow time for the PNG file ## to be completely downloaded before fetching the next ## PNG file in the 'row'. ############################################################### after 500 } ## END OF the INNER-loop over column index 'n'. ############################################################### ## Call the 'convert' program to horizontally concatenate ## this string of PNG filenames into a row-image file. ## '+append' appends horizontally. ## '-append' appends vertically. ############################################################### set ROWconcatfile "$DIRtemp/${ROWprefix}_${m}.png" eval exec $CONVERTpgm +append $ROWtilenames $ROWconcatfile set ROWfiles "$ROWfiles $ROWconcatfile" } ## END OF the OUTER-loop over row index 'm'. ############################################################### ## At the end of the double-loop, ## call the 'convert' program to vertically concatenate ## set of row-files into the final map image file. ## '+append' appends horizontally. ## '-append' appends vertically. ############################################################### set FINALfile "$DIRtemp/$env(USER)_FINAL_MAP.png" eval exec $CONVERTpgm -append $ROWfiles $FINALfile ############################################################### ## Remove the temporary tile-files and row-files. ############################################################### # exec rm $DIRtemp/${TILEprefix}*.png # exec rm $DIRtemp/${ROWprefix}*.png ############################################################### ## A 'background' run of the viewer --- with & --- per page 105 ## of the 4th edition of 'Practical Programming in Tcl & Tk. ## ## (We want to run the viewer in the background --- with & --- ## because we do want to allow the user to go on to make ## another map file while leaving this one in a viewer window. ## ## We MAY need to use 'eval' to avoid a 'not found' error if ## we append some parms (or a space) to the command. ############################################################### set VARcommand "$IMGviewer $FINALfile" set RETcode [ catch {eval exec $VARcommand &} BackgroundResult ] if {$RETcode != 0} { set ERRtext \ "Proc 'make_PNG_map' seems to have encountered an error in trying to view local image-file $FINALfile with image-viewer $IMGviewer. RETcode: $RETcode Message/ViewerPID: $BackgroundResult Stopping processing. Make sure the named image-viewer is available." popup_msgVarWithScroll .topErr "$ERRtext" $POPUPloc return } ##+##################################################### ## Give the user some advice on how to make another map. ##+##################################################### advise_user "$aRtext(msgHOWtoMAKE)" } ## END OF PROC 'make_PNG_map' ##+##################################################################### ## PROC: reset_parms ## ## PURPOSE: ## Sets values in the entry fields on the GUI. ## ## CALLED BY: the 'ResetParms' proc. ##+#################################################################### proc reset_parms {} { ## FOR TESTING: (dummy out this proc) # return global ENTRYzoomLevel ENTRYlatitude ENTRYlongitude \ ENTRYcolsLeft ENTRYcolsRight ENTRYrowsVert1 ENTRYrowsVert2 # set ENTRYzoomLevel "16" # set ENTRYzoomLevel "14" set ENTRYzoomLevel "12" ## Lat-Lon for Canary Islands, Island: Tenerife, City: Santa Cruz de Tenerife ## according to Wikipedia: 28.4667, -16.2500 set ENTRYlatitude "28.4667" set ENTRYlongitude "-16.2500" ## The following entries are intended to capture tiles for the ## entire island of Tenerife. set ENTRYcolsLeft "8" set ENTRYcolsRight "1" set ENTRYrowsVert1 "6" set ENTRYrowsVert2 "1" } ## END OF PROC 'reset_parms' ##+##################################################################### ## PROC 'advise_user' ##+##################################################################### ## PURPOSE: Puts a message to the user on the GUI. ## ## CALLED BY: in the additional-GUI-initialization section at ## the bottom of this script ## and in some procs. ##+##################################################################### proc advise_user {text} { .fRmsg.labelMSG configure -text "$text" ## Make sure the text is displayed on the GUI. update ## Alternatively, we could put the message in the title-bar ## of the GUI window. (But it is easy for the user to ## fail to see the message there. Besides, we have more ## options in displaying the message by putting it on a ## Tk widget in the GUI.) ## # wm title . "$text" } ## END OF PROC 'advise_user' ##+######################################################################### ## PROC 'zlevel_increment' ##+######################################################################### ## PURPOSE: Increases the zoom-level in its entry widget. ## ## CALLED BY: the zoom-level '+' button ##+######################################################################### proc zlevel_increment {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYzoomLevel if {$ENTRYzoomLevel < 18} {incr ENTRYzoomLevel} } ## END OF PROC 'zlevel_increment' ##+######################################################################### ## PROC 'zlevel_decrement' ##+######################################################################### ## PURPOSE: Decreases the zoom-level in its entry widget. ## ## CALLED BY: the zoom-level '-' button ##+######################################################################### proc zlevel_decrement {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYzoomLevel if {$ENTRYzoomLevel > 0} {incr ENTRYzoomLevel -1} } ## END OF PROC 'zlevel_decrement' ##+######################################################################### ## PROC 'colsleft_increment' ##+######################################################################### ## PURPOSE: Increases the cols-left in its entry widget. ## ## CALLED BY: the cols-left '+' button ##+######################################################################### proc colsleft_increment {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYcolsLeft TILESlimit if {$ENTRYcolsLeft < $TILESlimit} {incr ENTRYcolsLeft} } ## END OF PROC 'colsleft_increment' ##+######################################################################### ## PROC 'colsleft_decrement' ##+######################################################################### ## PURPOSE: Decreases the cols-left in its entry widget. ## ## CALLED BY: the cols-left '-' button ##+######################################################################### proc colsleft_decrement {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYcolsLeft if {$ENTRYcolsLeft > 0} {incr ENTRYcolsLeft -1} } ## END OF PROC 'colsleft_decrement' ##+######################################################################### ## PROC 'colsright_increment' ##+######################################################################### ## PURPOSE: Increases the cols-right in its entry widget. ## ## CALLED BY: the cols-right '+' button ##+######################################################################### proc colsright_increment {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYcolsRight TILESlimit if {$ENTRYcolsRight < $TILESlimit} {incr ENTRYcolsRight} } ## END OF PROC 'colsright_increment' ##+######################################################################### ## PROC 'colsright_decrement' ##+######################################################################### ## PURPOSE: Decreases the cols-right in its entry widget. ## ## CALLED BY: the cols-right '-' button ##+######################################################################### proc colsright_decrement {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYcolsRight if {$ENTRYcolsRight > 0} {incr ENTRYcolsRight -1} } ## END OF PROC 'colsright_decrement' ##+######################################################################### ## PROC 'rowsvert1_increment' ##+######################################################################### ## PURPOSE: Increases the rows-above in its entry widget. ## ## CALLED BY: the rows-above '+' button ##+######################################################################### proc rowsvert1_increment {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYrowsVert1 TILESlimit if {$ENTRYrowsVert1 < $TILESlimit} {incr ENTRYrowsVert1} } ## END OF PROC 'rowsvert1_increment' ##+######################################################################### ## PROC 'rowsvert1_decrement' ##+######################################################################### ## PURPOSE: Decreases the rows-above in its entry widget. ## ## CALLED BY: the rows-above '-' button ##+######################################################################### proc rowsvert1_decrement {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYrowsVert1 if {$ENTRYrowsVert1 > 0} {incr ENTRYrowsVert1 -1} } ## END OF PROC 'rowsvert1_decrement' ##+######################################################################### ## PROC 'rowsvert2_increment' ##+######################################################################### ## PURPOSE: Increases the rows-below in its entry widget. ## ## CALLED BY: the rows-below '+' button ##+######################################################################### proc rowsvert2_increment {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYrowsVert2 TILESlimit if {$ENTRYrowsVert2 < $TILESlimit} {incr ENTRYrowsVert2} } ## END OF PROC 'rowsvert2_increment' ##+######################################################################### ## PROC 'rowsvert2_decrement' ##+######################################################################### ## PURPOSE: Decreases the rows-below in its entry widget. ## ## CALLED BY: the rows-below '-' button ##+######################################################################### proc rowsvert2_decrement {} { ## FOR TESTING: (to dummy out this proc) # return global ENTRYrowsVert2 if {$ENTRYrowsVert2 > 0} {incr ENTRYrowsVert2 -1} } ## END OF PROC 'rowsvert2_decrement' ##+#################################################################### ## PROC: 'edit_inputs' ##+##################################################################### ## PURPOSE: Checks entry widgets entries and pops up an error message ## if the data is invalid. ## ## CALLED BY: the 'rePlot' proc ##+##################################################################### proc edit_inputs {} { ## Decimal/floating-point variables. global ENTRYlatitude ENTRYlongitude ## Integer variables. global ENTRYzoomLevel ## Error indicator --- and window-location for popup error windows. global EDITcode POPUPloc ## We could do without the EDITcode variable, by using ## a code with the 'return' statement herein. But using this ## code variable is a little more self-documenting. set EDITcode 0 ####################################################### ## Remove trailing and leading blanks (if any) from the ## user entries in the 'entry' widgets. ####################################################### set ENTRYlatitude [string trim $ENTRYlatitude] set ENTRYlongitude [string trim $ENTRYlongitude] set ENTRYzoomLevel [string trim $ENTRYzoomLevel] ######################################################################### ## Check that these entry fields are NOT blank. ######################################################################### set MSGblank "is blank. Must NOT be blank." if {"$ENTRYlatitude" == ""} { popup_msgVarWithScroll .topErr "The LATITUDE entry field $MSGblank" $POPUPloc set EDITcode 1 return } if {"$ENTRYlongitude" == ""} { popup_msgVarWithScroll .topErr "The LONGITUDE entry field $MSGblank" $POPUPloc set EDITcode 1 return } if {"$ENTRYzoomLevel" == ""} { popup_msgVarWithScroll .topErr "The ZOOM-LEVELS entry field $MSGblank" $POPUPloc set EDITcode 1 return } ############################################################# ## Check that ENTRYzoomLevel contains only positive integers ## and spaces. ############################################################# set MSGnotIntegersSpaces " is NOT a POSITIVE INTEGER or a SPACE." set STRlen [string length "$ENTRYzoomLevel"] for {set idx 0} {$idx < $STRlen} {incr idx} { set char [string index "$ENTRYzoomLevel" $idx] set MATCHcode [ string match {[0-9 ]} $char ] ## FOR TESTING: if {0} { puts "" puts "PROC 'edit_inputs' :" puts " ENTRYzoomLevel: $ENTRYzoomLevel" puts " STRlen: $STRlen" puts " char: $char" puts " MATCHcode: $MATCHcode" } if {$MATCHcode == 0} { popup_msgVarWithScroll .topErr \ "A character in the ZOOM-LEVELS entry field $MSGnotIntegersSpaces" $POPUPloc set EDITcode 1 return } } ########################################################################## ## Check that each component of ENTRYzoomLevel is an integer less than 19. ########################################################################## set MSGnotSmallInteger " is NOT INTEGER less than 19." foreach z $ENTRYzoomLevel { if {$z > 18} { popup_msgVarWithScroll .topErr \ "The string '$z' of the ZOOM-LEVELS $MSGnotSmallInteger" $POPUPloc set EDITcode 1 return } } ######################################################################### ## Check that ENTRYlatitude and ENTRYlongitude ## are decimal numbers (positive or negative) ## --- such as 1.234 or -3 or -3.0 or -.4 or .5 or 7 ######################################################################### ## Implemented using the 'decimal_check' proc below. ######################################################################### set NUMERICmsg "should be a decimal number. Examples: 1.234 or 0.56 or -.789" if {![decimal_check "$ENTRYlatitude"]} { popup_msgVarWithScroll .topErr "The LATITUDE parameter $NUMERICmsg" $POPUPloc set EDITcode 1 return } if {![decimal_check "$ENTRYlongitude"]} { popup_msgVarWithScroll .topErr "The LONGITUDE parameter $NUMERICmsg" $POPUPloc set EDITcode 1 return } ####################################################################### ## Check that ENTRYnonnegnumber is not negative. ## COMMENTED, for no. ####################################################################### if {0} { set POSITIVEmsg "should be a POSITIVE number. Examples: 0.001 or 0.56" if {$ENTRYwhatever < 0.0} { popup_msgVarWithScroll .topErr "The WHATEVER value $POSITIVEmsg" $POPUPloc set EDITcode 1 return } } } ## END OF PROC 'edit_inputs' ##+######################################################################## ## PROC: 'popup_msgVarWithScroll' ##+######################################################################## ## PURPOSE: Report help or error conditions to the user. ## ## We do not use focus,grab,tkwait in this proc, ## because we use it to show help when the GUI is idle, ## and we may want the user to be able to keep the Help ## window open while doing some other things with the GUI ## such as putting a filename in the filename entry field ## or clicking on a radiobutton. ## ## For a similar proc with focus-grab-tkwait added, ## see the proc 'popup_msgVarWithScroll_wait' in a ## 3DterrainGeneratorExaminer Tk script. ## ## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk', ## 4th edition, by Welch, Jones, Hobbs. ## ## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg) ## and a variable holding text (many lines, if needed). ## ## CALLED BY: 'help' button ##+######################################################################## ## To have more control over the formatting of the message (esp. ## words per line), we use this 'toplevel-text' method, ## rather than the 'tk_dialog' method -- like on page 574 of the book ## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications ## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor". ##+######################################################################## proc popup_msgVarWithScroll { toplevName VARtext ULloc} { ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global. ## global env # bell # bell ################################################# ## Set VARwidth & VARheight from $VARtext. ################################################# ## To get VARheight, ## split at '\n' (newlines) and count 'lines'. ################################################# set VARlist [ split $VARtext "\n" ] ## For testing: # puts "VARlist: $VARlist" set VARheight [ llength $VARlist ] ## For testing: # puts "VARheight: $VARheight" ################################################# ## To get VARwidth, ## loop through the 'lines' getting length ## of each; save max. ################################################# set VARwidth 0 ############################################# ## LOOK AT EACH LINE IN THE LIST. ############################################# foreach line $VARlist { ############################################# ## Get the length of the line. ############################################# set LINEwidth [ string length $line ] if { $LINEwidth > $VARwidth } { set VARwidth $LINEwidth } } ## END OF foreach line $VARlist ## For testing: # puts "VARwidth: $VARwidth" ############################################################### ## NOTE: VARwidth works for a fixed-width font used for the ## text widget ... BUT the programmer may need to be ## careful that the contents of VARtext are all ## countable characters by the 'string length' command. ############################################################### ##################################### ## SETUP 'TOP LEVEL' HELP WINDOW. ##################################### catch {destroy $toplevName} toplevel $toplevName # wm geometry $toplevName 600x400+100+50 # wm geometry $toplevName +100+50 wm geometry $toplevName $ULloc wm title $toplevName "Note" # wm title $toplevName "Note to $env(USER)" wm iconname $toplevName "Note" ##################################### ## In the frame '$toplevName' - ## DEFINE THE TEXT WIDGET and ## its two scrollbars --- and ## DEFINE an OK BUTTON widget. ##################################### if {$VARheight > 10} { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" ## -font fontTEMP_varwidth scrollbar $toplevName.scrolly \ -orient vertical \ -command "$toplevName.text yview" scrollbar $toplevName.scrollx \ -orient horizontal \ -command "$toplevName.text xview" } else { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 ## -font fontTEMP_varwidth } button $toplevName.butt \ -text "OK" \ -font fontTEMP_varwidth \ -command "destroy $toplevName" ############################################### ## PACK *ALL* the widgets in frame '$toplevName'. ############################################### ## Pack the bottom button BEFORE the ## bottom x-scrollbar widget, pack $toplevName.butt \ -side bottom \ -anchor center \ -fill none \ -expand 0 if {$VARheight > 10} { ## Pack the scrollbars BEFORE the text widget, ## so that the text does not monopolize the space. pack $toplevName.scrolly \ -side right \ -anchor center \ -fill y \ -expand 0 ## DO NOT USE '-expand 1' HERE on the Y-scrollbar. ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA. pack $toplevName.scrollx \ -side bottom \ -anchor center \ -fill x \ -expand 0 ## DO NOT USE '-expand 1' HERE on the X-scrollbar. ## THAT KEEPS THE TEXT AREA FROM EXPANDING. pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } else { pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } ################################################ ## Set some 'event' bindings to allow for ## easy scrolling through huge listings. ## is a press of the Page-Down key. ## is a press of the Page-Up key. ## is a press of the Home key ## to go to the top of the listing. ## is a press of the End key ## to go to the bottom of the listing. ## is a press of the Up-arrow key. ## is a press of the Down-arrow key. ################################################ bind $toplevName "$toplevName.text yview scroll +1 page" bind $toplevName "$toplevName.text yview scroll -1 page" bind $toplevName "$toplevName.text see 1.0" bind $toplevName "$toplevName.text see end" bind $toplevName "$toplevName.text yview scroll -1 unit" bind $toplevName "$toplevName.text yview scroll +1 unit" ##################################### ## LOAD MSG INTO TEXT WIDGET. ##################################### ## $toplevName.text delete 1.0 end $toplevName.text insert end $VARtext $toplevName.text configure -state disabled } ## END OF PROC 'popup_msgVarWithScroll' ##+######################## ## END of PROC definitions. ##+######################## ## Set HELPtext var. ##+######################## set HELPtext \ " ** HELP for this Make a PNG-file MAP (from OSM Tiles) utility ** ** For a given OSM 'zoom-level' ** ** AND for a given latitude and longitude (in decimal degrees) ** ** AND for a specified number of tiles around a 'central' tile ** ** this utility makes a map in a single PNG file. ** (OSM = Open Street Map) This Tk script provides a GUI for entering (or selecting) a latitude-longitude location on the Earth (in decimal degrees). (A 'Sites-LatLon' button on the GUI brings up a selector GUI that contains a list of sites --- countries, states, cities, parks, etc. By selecting a site, the latitude-longitude of its center-location is put in the Latitude-Longitude entry fields of the PNG-map-maker GUI.) In addition to a latitude-longitude specification, the PNG-map-maker GUI also allows the user to enter an OSM zoom-level --- where an OSM 'zoom-level' indicator is an integer between 0 and 18. The GUI also allows the user to set integers (M1,M2,N1,N2) that specify how many tiles on the left,right,top,bottom around a 'central' tile (determined from the user-specified latitude and longitude) are to be retrieved from an OSM PNG-file server. (The integers M1,M2,N1,N2 are suggested to be from zero to 16 --- no more than 16. At zoom-level 18, there are 4^18 = 68,719,476,736 = 68 trillion OSM tiles covering the Earth. It is not a good idea to try to build a single PNG file from anywhere near that many 256 x 256 pixel tiles.) The 4 integers M1,M2,N1,N2 are used to specify that M1+1+M2 = M columns and N1+1+N2 = N rows of tiles (256x256 pixel PNG files) are to be joined to make a single PNG file --- which provides a map of the specified region. Reference: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels ******************* METHOD OF OPERATION: ******************* After the user specifies on the GUI - latitude and longitude (in decimal degrees) - zoom-level (an integer between 0 and 18) - integers M1,M2,N1,N2 (for numbers of tiles left,right,above,below a central tile), then the user can click on the 'MakePNGfileMAP' button to make the PNG file. The PNG file is made as follows: 1) The latitude-longitude (in decimal degrees) and the specified zoom-level ('z') determines a pair of integers M0,N0 (by an OSM algorithm) that determine an OSM tile. The integers z,M0,N0 determine an internet URL that specifies a 256x256 PNG filename. The URL format: http://tile.openstreetmap.org/z/M0/N0.png (The N-position integers correspond to latitudes --- row-number of tiles.) (The M-position integers correspond to longitudes --- column-number of tiles.) (In other words, fixing an M number and letting N vary determines a column of tiles --- along a north-south longitude line.) 2) The 'wget' command is used to fetch a total of NxM PNG files --- where there will be N1+1+N2=N rows of tiles and M1+1+M2=M columns of tiles. These NxM tile files are put into a local directory, such as /tmp. 3) The fetched PNG files are concatenated into rows of tiles. For row Concatenate columns ----------------- ------------------------------------------------- (N0 - N1) (M0 - M1), ... , M0 , ... , (M0 + M2) (N0 - N1 + 1) (M0 - M1), ... , M0 , ... , (M0 + M2) ...... ...... (N0 - 1) (M0 - M1), ... , M0 , ... , (M0 + M2) N0 (M0 - M1), ... , M0 , ... , (M0 + M2) (N0 + 1) (M0 - M1), ... , M0 , ... , (M0 + M2) ...... ...... (N0 + N2 - 1) (M0 - M1), ... , M0 , ... , (M0 + M2) (N0 + N2) (M0 - M1), ... , M0 , ... , (M0 + M2) Then the row images are concatenated into the final PNG image file composed of NxM tiles. (The ImageMagick 'convert' command is used to join the tiles.) 4) The resulting image of NxM joined tiles is shown in an image viewer --- an image viewer that the user can specify in a variable 'IMGviewer' set at the bottom of this script. Examples: set IMGviewer \"/usr/bin/eog\" or set IMGviewer \"/usr/bin/eom\" to specify the EyeOfGnome or EyeOfMATE image viewer on Linux. 5) The user can close the image viewer window and then specify another - Earth location (latitude and longitude) and/or - zoom level and/or - left/right/above/below integers (M1,M2,N1,N2) and do another fetch-and-concatenate-and-show by clicking on the 'MakePNGfileMAP' button. ************************************************************* ANOTHER TK SCRIPT TO HELP DETERMINE AN APPROPRIATE ZOOM-LEVEL: ************************************************************* To get an idea of the zoom-level to use, there is a separate Tk GUI ('tkGooie') utility that allows the user to easily show some OSM tiles (256x256 PNG files) at a specific Earth location --- for multiple zoom levels. This separate multiple-zoom-levels 'tkGooie' script allows the user to enter a latitude-longitude location --- or select a site on the Earth (city, state, country, national park, etc.) --- like this 'Make-PNG-file-MAP' GUI utility. The multiple-zoom-levels 'tkGooie' script then pops up the multiple zoom-level tiles, at the latitude-longitude location, in a PNG image-viewer --- like this 'Make-PNG-file-MAP' GUI utility. The separate multiple-zoom-levels 'tkGooie' script could be called (someday?) by this 'Make-PNG-file-MAP' GUI --- say, via a 'CheckZoomLevels' button on this GUI. For now, you can access that multiple-zoom-levels 'tkGooie' script in the 'MAPtools' toolchest of the FE 'tkGooies' menu/toolchest system. ************************** NOTES ON OTHER PNG VIEWERS: ************************** Other 'simple', quick-starting image VIEWER programs --- besides 'eog' or 'eom' --- that might be useful are 'ffplay', 'feh', 'gwenview', 'gpicview', ImageMagick 'display' or 'animate', and quite a few others. Also, 'light-weight' image EDITOR programs --- like 'mtpaint' --- could be used as a PNG viewer. " ##+###################################################### ## ADDITIONAL GUI INITIALIZATION section. ##+###################################################### ##+###################################################### ## Set angle parms for use in a lat-lon-TO-OSM-integers ## conversion script. ##+###################################################### set pi [expr {4.0 * atan(1.0)}] set radsPERdeg [expr {$pi/180.0}] ##+##################################################### ## Set full-name for a Tk GUI script that is to be used ## to return latitude,longitude (in decimal degrees) ## for a user-selected site (city, state, country, ## national park, whatever). ##+##################################################### ## FOR TESTING: # puts "argv0: $argv0" set DIRthisScript "[file dirname $argv0]" ## For ease of testing in a Linux/Unix terminal and located at the ## directory containing this Tk script. Set the full directory name. if {"$DIRthisScript" == "."} { set DIRthisScript "[pwd]" } ## Choose one of two locations for the LatLonSelector Tk script. ## You may change '{1}' to '{0}', to use a LatLonSelector that is ## placed in the directory of this script. if {1} { ## This is the location of the LatLonSelector in the directory structure ## of the FE 'tkGooies' system. set DIRupOne "[file dirname "$DIRthisScript"]" set DIRupTwo "[file dirname "$DIRupOne"]" set SELECTORlatlon "$DIRupTwo/SELECTORtools/tkSiteLatitudeLongitudeSelector/tkSiteLatitudeLongitudeSelector.tk" } else { ## Alternatively: Put the LatLon-selector Tk script in the ## same directory as this FetchOSMtiles Tk script and use the following. set SELECTORlatlon "$DIRthisScript/tkSiteLatitudeLongitudeSelector.tk" } ##+############################################ ## Set a (temp) directory for fetched PNG files. ##+############################################ set DIRtemp "/tmp" ##+############################################ ## Set a filename-prefix for fetched PNG files ## and temporary merged 'row' tiles. ##+############################################ set TILEprefix "$env(USER)_tile_" set ROWprefix "$env(USER)_row_" ##+##################################################### ## Set full-name for the 'wget' program ## that is to be used to fetch PNG files. ##+##################################################### set WGETpgm "/usr/bin/wget" ##+####################################################### ## Set a wait-time to pause to allow for full retrieval ## of an image from an OpenStreetMap server before trying ## to display the image in an image viewer. ## ## For a faster image viewer, you may want to use a ## lower wait value. ##+####################################################### # set WAITmillisecs 500 set WAITmillisecs 800 ##+####################################################### ## Set a variable to hold the location of the popup ## window used for error messages. ## Used in proc 'edit_inputs'. ##+####################################################### set POPUPloc "+30+30" ##+##################################################### ## Set full-name for the ImageMagick 'convert' program ## that is to be used to concatenate PNG files. ##+##################################################### set CONVERTpgm "/usr/bin/convert" ##+##################################################### ## Set full-name for the executable of an image viewer. ##+##################################################### # set IMGviewer "/usr/bin/ffplay" # set IMGviewer "/usr/bin/eog" # set IMGviewer "/usr/bin/eom" # set IMGviewer "/usr/bin/display" # set IMGviewer "/usr/bin/animate" # set IMGviewer "/usr/bin/mtpaint -v" ;# For view-only use of 'mtpaint'. set IMGviewer "/usr/bin/mtpaint" # set IMGviewer "/usr/bin/evince" # set IMGviewer "/usr/bin/gimp" ;# This is overkill. Slow start. # set IMGviewer "/usr/bin/xpaint" ;# This is overkill. Too many windows/menus. ##+######################################################## ## Set a limit on the number of tiles that can surround ## the 'central' tile. ## ## The max number of tiles in the horizontal or vertical ## direction is then limited to TILESlimit + 1 + TILESlimit. ## ## For TILESlimit=16, this means a max of 33 tiles in the ## horizontal or vertical direction. ## ## Since the tiles are 256x256 pixels square, the maximum ## number of pixels in the x and y directions in the ## 'final map' file is then 33x256 = 8448 pixels. ## ## The maximum size of the 'final map' PNG file is then ## 8448x8448 pixels. This is about 4 times higher and ## wider than ultra-hi-res monitors of about 2K x 2K pixels. ##+######################################################## set TILESlimit 16 ##+########################################################### ## Initialize the entry fields: ## ENTRYzoomLevel ENTRYlatitude ENTRYlongitude ## ENTRYcolsleft ENTRYcolsright ENTRYrowsvert1 ENTRYrowsvert2 ##+########################################################### reset_parms ##+##################################################### ## Give the user some advice on how to start. ##+##################################################### advise_user "$aRtext(msgHOWtoMAKE)"