#!/usr/bin/wish -f ## ## SCRIPT: tkReadOutlineFile_WriteFilteredFile.tk ## ## PURPOSE: This Tk GUI script reads an 'outline' file whose lines contain pairs ## of coordinates (example: longitude and latitude in decimal degrees) ## and writes a new 'outline' file by using a 'filter distance' value ## to skip writing any point-line when that point is very close ## to the previously read point. ## ## NOTE: The outline data does not have to be map (longitude,latitude) ## data. It could be pairs of coordinates that provide an ## outline of almost any object --- for example a silouette of a ## person or an animal or an insect or a fish or ## a logo or alphanumeric characters in different fonts ## or geometric figures (like pentagons, hexagons, ...) ## or mosaic patterns or whatever. ## ## In addition to using the 'filter distance' value to reduce the ## number of records chosen from the input outline data file, this ## utility provides the option to filter according to ## Xmin, Xmax, Ymin, Ymax values that can be entered in 4 'entry' ## widgets on the GUI. A 'checkbutton' widget on the GUI determines ## whether this X-Y-limits filter is applied. ## ##+################ ## READING THE DATA (and sources of data) : ##+################ ## ## The x,y (e.g. longitude,latitude) data in the input file is assumed ## to be in ASCII format, NOT binary. ## ## There are many country/continent/state boundary/outline data files ## on the internet in *ASCII* format. Examples: ## - GeoJSON files ## References: ## https://en.wikipedia.org/wiki/JSON ## https://en.wikipedia.org/wiki/GeoJSON ## geojson.org ## json.org ## - KML and KMZ files (where KML = Keyhole Markup Language) ## References: ## https://en.wikipedia.org/wiki/Keyhole_Markup_Language ## http://www.opengeospatial.org/standards/kml/ ## https://developers.google.com/kml/documentation/?csw=1 ## ## There is a lot of XML-like extra markup language in '.geojson' ## and '.kml'/'.kmz' files. ## ## For input to this utility, we take those files and 'clean them up' ## so that there are only a pair of space-separated (or comma-separated) ## decimal numbers in each line --- and some comment lines indicated by ## '#' in column one of each comment line. ## ## This utility reads input files in this very simple, minimal format. ## Thus, we avoid the need to add to this utility the ability to read ## '.geojson' and '.kml' (and '.kmz') files --- which would require ## very elaborate parsing logic in order to handle the many ways in ## which people create these KML and JSON files. ## ## (Note: We include, in the file-reading-writing, the ability to ## replace any comma character with a space. So this ## utility can read CSV = Comma-Separated-Variable files ## and writes out 'space-separated' files.) ## ## In reading the file, some data statistics may be gathered --- such as ## the min,max x and y values. At the conclusion of reading the input file, ## the statistics can be shown in a message area on the GUI. ## ##+############# ## GUI FEATURES: ## ## The GUI is to allow the user to ## - enter or get a filename --- into an 'entry' widget ## - specify a 'filter-distance' value --- in an 'entry' widget ## - optionally, allow for using Xmin,Xmax,Ymin,Ymax values to filter ## out records from the input file ## - (re)read the file (and write the new file) --- via a 'button' widget ## ##+############## ## THE GUI LAYOUT: ## ## One way the user can specify these parameters is indicated by ## the following 'sketch' of the GUI: ## ## FRAMEnames ## VVVVVVVVVV ## ------------------------------------------------------------------------------------------ ## Read an Outline File (e.g. Longitudes-Latitudes) - and Write a 'Filtered' Outline File ## [window title] ## ------------------------------------------------------------------------------------------ ## ## .fRbuttons {Exit} {Help} {Read-Write} ## ## .fRfile Enter outline-data filename: ______________________________________________ {Browse...} ## ## .fRmsg [ .......... Messages go here, in a label widget .................................... ] ## ## .fRparms Filter Distance: 0.01___ (to filter out any point close to a previous point) ## [a label here contains the text above] ## ## .fRlimits X Filter by Min-X: -45.0___ Max-X: 45.0___ Min-Y: -45.0___ Max-Y: 45.0___ ## X-Y limits ## ## ------------------------------------------------------------------- ## ## 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. ## CAPITAL-X indicates a Tk 'checkbutton' widget (if any). ## ## VERTICAL-BARS and horizontal HYPHENS are used to outline a 'canvas' or ## 'listbox' or 'text' widget. ## ## If there are scrollbars: ## Less-than and greater-than signs indicate the left and right ends of a horizontal 'scrollbar'. ## Capital-V and Capital-A letters indicate the bottom and top ends of a vertical 'scrollbar'. ## ##+############## ## GUI components: ## ## From the GUI 'sketch' above, it is seen that the GUI consists of about ## ## - 4 button widgets ## - 8 label widgets ## - 6 entry widgets ## - 1 checkbutton widget ## - 0 radiobutton widgets ## - 0 canvas widgets ## - 0 scale widgets ## - 0 listbox widgets ## - 0 text widgets ## ##+######################################################################## ## 'CANONICAL' STRUCTURE OF THIS TK CODE: ## ## 0) Set general window & widget parms (win-name, win-position, ## win-color-scheme, fonts, widget-geometry-parms, win-size-control). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack ALL frames and sub-frames. ## ## 2) Define all widgets in the frames. Pack them. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen action ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or two of ## the procs), if needed. ## ## ## Some detail about the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : '.fRbuttons' ## '.fRfile' ## '.fRmsg' ## '.fRparms' ## '.fRlimits' ## No sub-frames. ## ## 1b) Pack ALL frames. ## ## 2) Define all WIDGETS in the frames (and pack them): ## ## - In '.fRbuttons': ## about 3 button widgets ('Exit','Help','Read-Write') ## ## - In '.fRmsg': ## 1 label widget to display messages to the user ## ## - In '.fRfile': ## 1 label and 1 entry and 1 button widget ## ## - In '.fRparms': ## 1 pair of 'label' and 'entry' widgets ## ## 3) Define BINDINGS: see the BINDINGS section for bindings, if any ## ## 4) Define PROCS, such as: ## ## - 'get_filename' - called by the 'Browse...' button ## ## - 'read-file_write-file' - called by the 'Read-Write' button ## ## - 'advise_user' - called by the 'read-file_write-file' proc ## and in the 'Additional GUI Initialization' ## section at the bottom of this script ## ## - 'edit_inputs' - called by the 'read-file_write-file' proc ## ## - 'decimal_check' - called by the 'edit_inputs' proc ## ## - 'numeric_check_string_double' - called by the 'read-file_write-file' proc ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button ## ## See the PROCS section for additional details. ## ## 5) Additional GUI initialization: ## Set some inital values of parameters ## such as some directory-name variables. ## ##+######################################################################## ## DEVELOPED WITH: ## Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala'). ## ## $ wish ## % puts "$tcl_version $tk_version" ## showed 8.5 8.5 on Ubuntu 9.10 ## after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts. ##+####################################################################### ## MAINTENANCE HISTORY: ## Created by: Blaise Montandon 2016nov18 ## Changed by: Blaise Montandon 2016dec03 Changed iconname. ## Added a new msg to the aRtext array. ## Chgd a suggested filter-distance msg. ## Chgd msg widget height from 5 to 6. ##+####################################################################### ##+####################################################################### ## Set general window parms (win-title,win-position). ##+####################################################################### wm title . "Read an Outline File (e.g. Longitude-Latitude points) - and Write a 'Filtered' Outline File" wm iconname . "FilterOutline" wm geometry . +15+30 ##+###################################################### ## Set the color scheme for the window and set the ## background color for the 'trough' in some widgets. ##+###################################################### tk_setPalette "#e0e0e0" set entryBKGD "#fcfcfc" set chkbuttBKGD "#f0f0f0" # set radbuttBKGD "#f0f0f0" # set scaleBKGD "#f0f0f0" # set listboxBKGD "#f0f0f0" ##+######################################################## ## Use a VARIABLE-WIDTH FONT for label and button widgets. ## ## Use a FIXED-WIDTH FONT for listboxes (and ## entry fields, if any). ##+######################################################## 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) ##+########################################################### ## BUTTON geom parameters: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 set RELIEF_button "raised" ## LABEL geom parameters: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 # set RELIEF_label "ridge" # set RELIEF_label "raised" set RELIEF_label "flat" ## ENTRY widget geom settings: set BDwidthPx_entry 2 set ParmEntryWidthChars 7 ## CHECKBUTTON geom parameters: set PADXpx_chkbutt 0 set PADYpx_chkbutt 0 set BDwidthPx_chkbutt 2 ## COMMENT some geom settings, for now. if {0} { ## RADIOBUTTON widget geom settings: set BDwidthPx_radbutt 2 # set RELIEF_radbutt "ridge" set RELIEF_radbutt "raised" ## SCALE geom parameters: set BDwidthPx_scale 2 # set initScaleLengthPx 100 set scaleThicknessPx 10 set scaleRepeatDelayMillisecs 800 ## CANVAS geom parameters: set initCanWidthPx 300 set initCanHeightPx 300 set minCanHeightPx 24 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 } ## END OF COMMENTED geom settings ##+#################################################################### ## 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 widgets in 'fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(buttonREADWRITE) "Read-Write" set aRtext(buttonSETLIMITS) "ResetParms" ## For widgets in 'fRfile' frame: set aRtext(labelFILENAME) \ "Enter outline-data filename:" set aRtext(buttonBROWSE) \ "Browse ..." ## For widgets in 'fRparms' frame: set aRtext(labelFILTERA) "Point Filter Distance:" set aRtext(labelFILTERB) \ " (to filter out any point close to a previous point ; larger filters more)" ## For widgets in .fRlimits' frame: set aRtext(chkbuttFILTERLIMS) "Filter on X-Y limits" set aRtext(labelXMINLIM) " Min-X:" set aRtext(labelXMAXLIM) " Max-X:" set aRtext(labelYMINLIM) " Min-Y:" set aRtext(labelYMAXLIM) " Max-Y:" ## For some calls to the 'advise_user' proc: set aRtext(STARTmsg) \ "*** After selecting an input data file and setting filter parameter(s), *** *** click '$aRtext(buttonREADWRITE)' when ready to create the output file. ***" set aRtext(STARTmsgSHORT) \ "** Set filter(s) and click '$aRtext(buttonREADWRITE)' when ready to create the output file **" set aRtext(PARMCHGmsg) \ "** Click '$aRtext(buttonREADWRITE)' when ready to create the output file **" ## END OF if { "$VARlocale" == "en"} ##+################################################################### ## Set a MINSIZE of the window. ## ## For width, allow for the minwidth of the '.fRbuttons' frame: ## about 7 buttons (Exit,Help,ReDraw,ResetParms,Clear, ## BkgdColor,LineColor,TextColor). ## We want to at least be able to see the 'Exit' button. ## ## For height, allow ## 1 char high for the '.fRbuttons' frame, ## 1 char high for the '.fRfile' frame, ## 2 chars high for the '.fRmsg' frame, ## 1 char high for the '.fRparms' frame, ## 1 char high for the '.fRlimits' frame ##+################################################################### ## MIN WIDTH: set minWinWidthPx [font measure fontTEMP_varwidth \ "$aRtext(buttonEXIT) $aRtext(buttonHELP) $aRtext(buttonREADWRITE)"] ## Add some pixels to account for right-left-side window decoration ## (about 8 pixels), about 3 x 4 pixels/widget for borders/padding for ## 3 widgets. set minWinWidthPx [expr {20 + $minWinWidthPx}] ## MIN HEIGHT --- allow ## 1 char high for '.fRbuttons' ## 1 char high for '.fRfile' ## 2 chars high for '.fRmsg' ## 1 char high for '.fRparms' ## 1 char high for '.fRlimits' set CharHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {6 * $CharHeightPx}] ## Add about 28 pixels for top-bottom window decoration, ## about 5x4 pixels for each of the 5 stacked frames and their ## widgets (their borders/padding). set minWinHeightPx [expr {$minWinHeightPx + 48}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We allow the window to be resizable horizontally, but ## not vertically. wm resizable . 1 0 ## If you want to make the window un-resizable in both x and y directions, ## you can use the following statement. # wm resizable . 0 0 ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' '.fRfile' '.fRmsg' ## '.fRparms' '.fRlimits' ##+################################################################ ## 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 -borderwidth $BDwidthPx_frame frame .fRfile -relief $RELIEF_frame -borderwidth $BDwidthPx_frame # frame .fRfile -relief raised -borderwidth 2 frame .fRmsg -relief raised -borderwidth 2 frame .fRparms -relief $RELIEF_frame -borderwidth $BDwidthPx_frame # frame .fRlimits -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRlimits -relief raised -borderwidth 2 ##+############################## ## PACK the top-level FRAMES. ##+############################## pack .fRbuttons \ .fRfile \ .fRmsg \ .fRparms \ .fRlimits \ -side top \ -anchor nw \ -fill x \ -expand 0 ##+######################################################### ## OK. Now we are ready to define the widgets in the frames. ##+######################################################### ##+##################################################################### ## In the '.fRbuttons' FRAME --- ## DEFINE about 8 BUTTON widgets. ## Then PACK all these widgets. ##+##################################################################### 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" +10+10} button .fRbuttons.buttREADWRITE \ -text "$aRtext(buttonREADWRITE)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {read-file_write-file} ##+########################################### ## Pack the widgets in the 'fRbuttons' frame. ##+########################################### pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttREADWRITE \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRfile' FRAME ---- ## DEFINE 1 LABEL widget, 1 ENTRY widget, and 1 BUTTON widget. ## Then PACK all these widgets. ##+################################################################## label .fRfile.labelFILENAME \ -text "$aRtext(labelFILENAME)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Someday we may initialize this entry widget variable ## (like some others) in the 'Additional GUI Initialization' ## section at the bottom of this script --- say via the ## 'reset_parms' proc. set ENTRYfilename "" entry .fRfile.entryFILENAME \ -textvariable ENTRYfilename \ -width 62 \ -bg $entryBKGD \ -disabledbackground $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry button .fRfile.buttonBROWSE \ -text "$aRtext(buttonBROWSE)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command { get_filename advise_user "$aRtext(STARTmsgSHORT)." } ##################################### ## Pack the widgets in frame '.fRfile'. ##################################### pack .fRfile.labelFILENAME \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRfile.entryFILENAME \ -side left \ -anchor w \ -fill x \ -expand 1 pack .fRfile.buttonBROWSE \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRmsg' FRAME ---- ## DEFINE-and-PACK 1 LABEL widget. ##+################################################################## label .fRmsg.labelINFO \ -text "" \ -height 6 \ -font fontTEMP_fixedwidth \ -justify left \ -anchor w \ -relief flat \ -bg "#ff6666" \ -bd $BDwidthPx_button pack .fRmsg.labelINFO \ -side left \ -anchor w \ -fill x \ -expand 1 ##+################################################################## ## In the '.fRparms' FRAME ---- ## DEFINE 1 pair of LABEL and ENTRY widgets. ## Then PACK all these widgets. ##+################################################################## label .fRparms.labelFILTERA \ -text "$aRtext(labelFILTERA)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYfiltertol "0.01" entry .fRparms.entryFILTERTOL \ -textvariable ENTRYfiltertol \ -width 9 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -relief sunken \ -bd $BDwidthPx_entry label .fRparms.labelFILTERB \ -text "$aRtext(labelFILTERB)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ##+##################################### ## PACK the widgets in frame '.fRparms'. ##+##################################### pack .fRparms.labelFILTERA \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRparms.entryFILTERTOL \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRparms.labelFILTERB \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRlimits' FRAME ---- ## DEFINE 1 CHECKBUTTON widget and 4 pairs of LABEL-and-ENTRY widgets. ## Then PACK all these widgets. ##+################################################################## ## The checkbutton variable, FILTERonLIMITS0or1, will be initialized ## in the 'Additional GUI Initialization' section at the bottom of ## this Tk script. Example: ## set FILTERonLIMITS0or1 0 checkbutton .fRlimits.chkbuttFILTERLIMS \ -text "$aRtext(chkbuttFILTERLIMS)" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_chkbutt \ -pady $PADYpx_chkbutt \ -bd $BDwidthPx_chkbutt \ -variable FILTERonLIMITS0or1 \ -selectcolor "$chkbuttBKGD" \ -relief raised ## For XMIN: label .fRlimits.labelXMINLIM \ -text "$aRtext(labelXMINLIM)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYxmin "-45.0" entry .fRlimits.entryXMINLIM \ -textvariable ENTRYxmin \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## For XMAX: label .fRlimits.labelXMAXLIM \ -text "$aRtext(labelXMAXLIM)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYxmax "45.0" entry .fRlimits.entryXMAXLIM \ -textvariable ENTRYxmax \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## For YMIN: label .fRlimits.labelYMINLIM \ -text "$aRtext(labelYMINLIM)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYymin "-45.0" entry .fRlimits.entryYMINLIM \ -textvariable ENTRYymin \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## For YMAX: label .fRlimits.labelYMAXLIM \ -text "$aRtext(labelYMAXLIM)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYymax "45.0" entry .fRlimits.entryYMAXLIM \ -textvariable ENTRYymax \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ##+##################################### ## PACK the widgets in frame '.fRlimits'. ##+##################################### pack .fRlimits.chkbuttFILTERLIMS \ .fRlimits.labelXMINLIM \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRlimits.entryXMINLIM \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRlimits.labelXMAXLIM \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRlimits.entryXMAXLIM \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRlimits.labelYMINLIM \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRlimits.entryYMINLIM \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRlimits.labelYMAXLIM \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRlimits.entryYMAXLIM \ -side left \ -anchor w \ -fill x \ -expand 0 ##+######################################## ## END OF the DEFINITION OF THE GUI WIDGETS ##+######################################## ##+############################### ## BINDINGS SECTION: ##+############################### ##+################################################################ ## Advise user on how to load the data from a selected file ## when the user clicks on the filename 'Browse' button. ##+################################################################ # bind .fRfile.buttonBROWSE \ # {advise_user "$aRtext(STARTmsgSHORT)."} ##+################################################################### ## Remind user to click the 'ReadWrite' button after changing some ## filter parameters. ##+################################################################### bind .fRparms.entryFILTERTOL \ {advise_user "NEW FILTER-DISTANCE VALUE may have been set. $aRtext(PARMCHGmsg) "} bind .fRlimits.entryXMINLIM \ {advise_user "NEW X-MIN drawing-area LIMIT VALUE may have been set. $aRtext(PARMCHGmsg) "} bind .fRlimits.entryXMAXLIM \ {advise_user "NEW X-MAX drawing-area LIMIT VALUE may have been set. $aRtext(PARMCHGmsg) "} bind .fRlimits.entryYMINLIM \ {advise_user "NEW Y-MIN drawing-area LIMIT VALUE may have been set. $aRtext(PARMCHGmsg) "} bind .fRlimits.entryYMAXLIM \ {advise_user "NEW Y-MAX drawing-area LIMIT VALUE may have been set. $aRtext(PARMCHGmsg) "} ##+################################################################### ## Enable or disable the min-max entry widgets according to the ## current setting of the 'Filter-min-max' checkbutton. ##+################################################################### bind .fRlimits.chkbuttFILTERLIMS \ {enable_disable_minmaxEntries} ##+###################################################################### ## PROCS SECTION: ## ## - 'get_filename' - called by the 'Browse...' button ## ## - 'read-file_write-file' - called by the 'ReadWrite' button ## ## - 'advise_user' - called by 'get_filename' and 'read...' procs ## and in the 'Additional GUI Initialization' ## section at the bottom of this script ## ## - 'edit_inputs' - called by the 'read-file_write-file' proc ## ## - 'decimal_check' - called by 'edit_inputs' proc ## ## - 'numeric_check_string_double' - called by proc 'read-file_write-file' ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button. ## ##+####################################################################### ######################################################################## ## PROC: 'get_filename' ######################################################################## ## PURPOSE: To use 'tk_getOpenFile' dialog to get a filename to put ## in the ENTRYfilename variable. ## ## CALLED BY: the 'Browse...' button ######################################################################## proc get_filename {} { global ENTRYfilename CURdir ENTRYxmin ENTRYxmax ENTRYymin ENTRYymax set fName "" set fName [tk_getOpenFile -parent . \ -title "Select a filename of a outline data file." \ -initialdir "$CURdir" ] ## FOR TESTING: # puts "fName : $fName" if {"$fName" == ""} {return} if {[file exists "$fName"]} { ################################################ ## If the filename from the file selector exits, ## put the name in the filename entry widget, ## removing any leading or trailing spaces. ############################################### set ENTRYfilename [string trim "$fName"] ################################################ ## Make the end of the filename visible in the ## filename entry fields. ################################################ .fRfile.entryFILENAME xview end ################################################# ## Save the current data directory, to quickly ## go to this directory if another file is ## to be selected for data loading. ################################################# set CURdir [ file dirname "$ENTRYfilename" ] ################################################# ## Clear the Xmin,Xmax,Ymin,Ymax entry fields of ## drawing-area limits from a previously loaded file ## --- in case that data does not correspond to ## this file. ## COMMENTED, for now. ################################################# # set ENTRYxmin "" # set ENTRYxmax "" # set ENTRYymin "" # set ENTRYymax "" } ###################################################### ## Put the focus on the filename entry field, to ## facilitate causing the data to be loaded from ## the file by simply pressing the Return/Enter key, ## without having to manually direct the focus there. ###################################################### focus .fRfile.entryFILENAME } ## END OF PROC 'get_filename' ######################################################################## ## PROC: 'read-file_write-file' ######################################################################## ## PURPOSE: Read the input file specified in ENTRYfilename and put all ## records except filtered data records into the output file. ## ## Rather than reading the entire input file and storing its ## x,y data points in Tcl arrays, we write each input record ## to the output file --- IF the record satisfies the filter ## criteria. ## ## When the 'filter distance' parameter is set ## sufficiently small, some data lines may be filtered out. ## ## If a record-read is not a comment record and not an ## empty record, it is assumed to be a data record --- ## that is, it is assumed that x,y numeric values are ## in columns 1 and 2 of the record. ## ## This proc checks the two columns in each record to see if ## they are numeric. If the 2 columns in any record are not ## numeric, a message is posted to the user and this proc ## is exited. ## ## This read proc assumes that whenever a separate 'loop' is ## started in an outline, a null line or comment line indicates ## the start of a new loop. All null lines and comment lines ## are written to the output file. ## ## CALLED BY: the 'ReadWrite' button ######################################################################## proc read-file_write-file {} { global ENTRYfilename DIRtemp env EDITcode ENTRYfiltertol \ FILTERonLIMITS0or1 ENTRYxmin ENTRYxmax ENTRYymin ENTRYymax \ TEXTeditor TEXTviewer ################################################################## ## Open the INPUT outline data file corresponding to the filename ## in the filename entry field, and get a file-handle identifier, 'f'. ################################################################## set f [open $ENTRYfilename] ## FOR TESTING: if {0} { puts "" puts "PROC 'read-file_write-file' :" puts " Opened file $ENTRYfilename" } ################################################################## ## Open the OUTPUT outline data file using a contructed filename in ## a directory for temporary files, and get a file-handle identifier, ## 'fout'. ################################################################## set OUTfilename "$DIRtemp/${env(USER)}_filtered_outline.txt" # catch {exec rm "$OUTfilename"} catch {file delete "$OUTfilename"} set fout [open $OUTfilename w] #################################################################### ## Initialize some variables to be used in the read-write loop below. #################################################################### set TotRecsRead 0 set line "" ## The 'filter distance' parm may make the number of datalines ## to be used for plotting less than the number of ## datalines read from the file. We keep track of both. set CNTdatalinesIN 0 set CNTdatalinesOUT 0 set INaNEWloop0or1 1 set CNTloop 0 ############################################################ ## START of the WHILE-LOOP for the 'gets' file-READING. ## ## The while-test below is equivalent to 'while {![eof $f]}'. ############################################################ while {[eof $f] == 0} { ############################################################ ## GET the next line (up to a line feed) --- and its length. ## Increment TotRecsRead by 1. ############################################################ set lineLen [gets $f line] incr TotRecsRead ################################################# ## If line too long, bail out of this read proc. ################################################# if { $lineLen > 1200 } { popup_msgVarWithScroll .topLineTooLong \ "STOPPED Reading File $ENTRYfilename because a very long line was encountered. ($lineLen characters) Record number: $TotRecsRead First 50 characters of the line: [ string range $line 0 50 ] " +30+30 close $f close $fout return } ################################################# ## Remove white space from both ends of the line. ################################################# set line [string trim $line] ########################################### ## Get the first character in the line. ########################################### set FIRSTchar [ string range $line 0 0 ] ################################################################# ## If the record is a comment record and the 1st record read, ## use its contents to set the variable VARtitle. ################################################################# if {"$FIRSTchar" == "#"} { if {$TotRecsRead == 1} { set VARtitle "$line" } } ###################################################### ## If the record is empty or a comment line, write it ## to the output file and 'continue' to read the next rec. ## ## A null record or comment line between data records ## is used to mean that a new loop/polygon is starting. ## We will want to increment 'CNTloop'. ## ## NOTE: ## If there are multiple null or comment records ## encountered, we could be incrementing 'CNTloop' ## when no data lines were found yet, if we increment ## CNTloop at every null or comment line. ## ## We use the variable 'INaNEWloop0or1' to ## indicate the start of a new loop and try not to ## increment 'CNTloop' unless we have hit a data ## line since the last loop. At that time, we set ## INaNEWloop0or1 to zero. ###################################################### if {"$line" == "" || "$FIRSTchar" == "#"} { set INaNEWloop0or1 1 puts $fout "$line" continue } ######################################################## ## At this point $line should not be empty or a comment ## line --- that is, $line should be an input data line. ## Count it. ######################################################## incr CNTdatalinesIN ######################################################## ## We first do some 'cleanup' on the data line --- ## concerning commas and tabs and multiple white-space ## characters. ######################################################## ## Replace each comma by a single space. ######################################################## set line [string map {\, \x20} $line] ######################################################## ## Replace each tab by a single space. ######################################################## set line [string map {\t \x20} $line] ######################################################## ## Replace multiple spaces by a single space. ## Ref: page 163 of 'Practical Programming in Tcl & Tk', ## 4th edition, by Welch, Hobbs, Jones. ######################################################## regsub -all {\s+} $line " " line ######################################################### ## Get the strings in the first 2 cols of the record. ## NOTE: ## If there is a 3rd column of data (a third number), ## we ignore it. ######################################################### set TEMPx [lindex $line 0] set TEMPy [lindex $line 1] ## FOR TESTING: if {0} { puts "" puts "Proc 'read-file_write-file':" puts " (Read a data line -- non-null and non-comment.)" puts " (We need to check if this is numeric data.)" puts " line: $line" puts " CNTdatalines: $CNTdatalines" puts " INaNEWloop0or1: $INaNEWloop0or1" puts " CNTloop: $CNTloop (Loop-count should not be incremented yet" puts " --- for the first point in the 'loop'.)" puts " TEMPx: $TEMPx" puts " TEMPy: $TEMPy" } ######################################################### ## If we get to this point, we want to write TEMPx,TEMPy ## to the output file. First ... ## ## We check the 2 values to see if they are floating-point ## numbers. If not, we pop a msg and exit this read proc. ########################################################## numeric_check_string_double $TEMPx if {$EDITcode != 0} { close $f close $fout return } numeric_check_string_double $TEMPy if {$EDITcode != 0} { close $f close $fout return } ######################################################## ## At this point, the 2 x,y values seem to be OK. ## ## If this IS the first DATA record read, initialize ## the vars that we use to save the min,max values ## of the x,y values in ALL the input recs. ## ## If this is NOT the first data rec, then update the ## min,max values of x,y. ######################################################## if {$CNTdatalinesIN == 1} { set Xmin $TEMPx set Xmax $TEMPx set Ymin $TEMPy set Ymax $TEMPy } else { if {$TEMPx < $Xmin} {set Xmin $TEMPx} if {$TEMPx > $Xmax} {set Xmax $TEMPx} if {$TEMPy < $Ymin} {set Ymin $TEMPy} if {$TEMPy > $Ymax} {set Ymax $TEMPy} } ######################################################## ## At this point, the 2 x,y values seem to be OK. ## ## If the checkbutton variable, FILTERonLIMITS0or1, is ## ON (equal to one), we check whether the x,y values ## are within the specified x,y limits. If not, then ## skip to the next input line. ######################################################## if {$FILTERonLIMITS0or1 == 1} { if {$TEMPx < $ENTRYxmin} {continue} if {$TEMPx > $ENTRYxmax} {continue} if {$TEMPy < $ENTRYymin} {continue} if {$TEMPy > $ENTRYymax} {continue} } ######################################################### ## The 2 x,y values seem to be OK and they have passed the ## x,y limits check (if activated), so we will ## write the 2 values to the output file, space-separated ## --- and save the 2 values in PREVx,PREVy. ## ## However, we have not done the 'filter-distance' check. ## ## If this IS the FIRST data line of a NEW LOOP, ## we do NOT do a filter-distance check, we increment ## CNTdatalinesOUT. Also we increment CNTloop and set ## INaNEWloop0or1 to zero. Then read the next line. ## ## If this is NOT the first point in the 'loop', we will ## do the filter-distance check. If the point does NOT pass, ## we 'continue' to read the next line. On the other hand, ## if the point DOES pass the filter-distance check, we ## increment CNTdatalinesOUT and write the 2 x,y values ## to the output file. ######################################################### if {$INaNEWloop0or1 == 1} { set INaNEWloop0or1 0 incr CNTloop incr CNTdatalinesOUT puts $fout "$TEMPx $TEMPy LoopCount: $CNTloop" set PREVx $TEMPx set PREVy $TEMPy ## FOR TESTING: if {0} { puts "" puts "PROC 'read-file_write-file' : (found a first point in a 'loop')" puts " line : $line" puts " Cur. lineLen : $lineLen" puts " TotRecsRead : $TotRecsRead" puts " CNTdatalinesIN : $CNTdatalinesIN" puts " CNTdatalinesOUT: $CNTdatalinesOUT" } ## Go get the next line. continue } ############################################################# ## At this point, we are at a data line that is NOT the ## first point of a new loop. We want to check if this ## data point passes the filter test. ## ## At this point, CNTdatalinesOUT contains the count of the ## the previously written point. ## ## If the distance between both x and y values of this point ## is closer than ENTRYfiltertol to the previous ## x and y values, skip to the next line. ############################################################# set DELx [expr {abs($TEMPx - $PREVx)}] set DELy [expr {abs($TEMPy - $PREVy)}] if {$DELx < $ENTRYfiltertol && $DELy < $ENTRYfiltertol} {continue} ################################################################# ## At this point, we are at a data line that is NOT the first ## point of a new loop --- and it passed the filter-distance test. ## We want to increment CNTdatalinesOUT and write the x,y ## values to the output file. We also write the current CNTloop ## value to the output file. ################################################################# incr CNTdatalinesOUT puts $fout "$TEMPx $TEMPy LoopCount: $CNTloop" set PREVx $TEMPx set PREVy $TEMPy ## FOR TESTING: if {0} { puts "" puts "PROC 'read-file_write-file' : (found a non-first point in a 'loop')" puts " line : $line" puts " Cur. lineLen : $lineLen" puts " TotRecsRead : $TotRecsRead" puts " CNTdatalinesIN : $CNTdatalinesIN" puts " CNTdatalinesOUT: $CNTdatalinesOUT" puts " TEMPx : $TEMPx" puts " TEMPy : $TEMPy" puts " CNTloop : $CNTloop" } } ## END OF while {![eof $f]} --- the data file reading #################################### ## Close the input and output files. #################################### close $f close $fout ################################################################# ## Set the ENTRY variables for xmin,xmax,ymin,ymax. ################################################################# set ENTRYxmin $Xmin set ENTRYxmax $Xmax set ENTRYymin $Ymin set ENTRYymax $Ymax ######################################################### ## If there are a lot of data lines in the file ## (say more than 200), in a message to the user, ## set a reasonable estimate for ENTRYfiltertol ## (a 'suggested' filter-distance value) --- ## as a proportion (say 0.003 - 3 parts in a thousand) ## of the smaller of xmax-minus-xmin and ## ymax-minus-ymin (in world coordinates). ################################################# set TOLmsg "" if {$CNTdatalinesIN > 200} { set TEMPdiffX [expr {$Xmax - $Xmin}] set TEMPdiffY [expr {$Ymax - $Ymin}] set TEMPdiff $TEMPdiffX if {$TEMPdiff > $TEMPdiffY} {set TEMPdiff $TEMPdiffY} set SUGGESTEDtol [expr {0.003 * $TEMPdiff}] # if {$SUGGESTEDtol < $ENTRYfiltertol} { set SUGGESTEDtolFMTD [format "%6.5f" $SUGGESTEDtol] set TOLmsg \ "** Based on the Xmin,Xmax,Ymin,Ymax values in the outline data file, you may want to try a ** 'Filter-Distance' value of about $SUGGESTEDtolFMTD (or larger) --- and re-read the file." # } } ## END OF if {$CNTdatalinesIN > 200} ############################################### ## Show the output file in a text editor (or a ## text viewer) --- if not too large. ############################################### set SHOWmsg "" if {$CNTdatalinesOUT < 100000} { # exec $TEXTviewer "$OUTfilename" & exec $TEXTeditor "$OUTfilename" & set SHOWmsg \ "The output file is shown in text editor $TEXTeditor." } ######################################################## ## Show how many lines were read from the input file ## and written to the output file (and some other stuff) ## --- in the message line of the GUI. ######################################################## advise_user \ "** FINISHED READING FILE. Total Lines Read: $TotRecsRead ** Total 'Filtered' Data Points/Lines Read: $CNTdatalinesOUT ** out of the Total Data Lines Read: $CNTdatalinesIN $TOLmsg ** $SHOWmsg" } ## END OF PROC 'read-file_write-file' ##+##################################################################### ## PROC: reset_parms ## ## PURPOSE: ## Sets values in the xmin,xmax,ymin,ymax fields on the GUI. ## ## CALLED BY: the 'ResetParms' proc. ##+#################################################################### proc reset_parms {} { ## FOR TESTING: (dummy out this proc) # return global ENTRYfiltertol ENTRYxmin ENTRYxmax ENTRYymin ENTRYymax # set ENTRYfiltertol "0.1" set ENTRYfiltertol "0.01" set ENTRYxmin "-45.0" set ENTRYxmax "45.0" set ENTRYymin "-45.0" set ENTRYymax "45.0" } ## END OF PROC 'reset_parms' ##+##################################################################### ## PROC: 'advise_user' ##+##################################################################### ## PURPOSE: Puts a message to the user on the GUI. ## ## CALLED BY: in the 'read-file_write-file' proc, ## in some 'bind' statements in the BIND section above, ## and in the 'Additional-GUI-Initialization' section at ## the bottom of this script. ##+##################################################################### proc advise_user {text} { .fRmsg.labelINFO 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: 'edit_inputs' ##+##################################################################### ## PURPOSE: Checks entry widgets entries and pops up an error message ## if the data is invalid. ## ## CALLED BY: the 'reDraw' proc ##+##################################################################### proc edit_inputs {} { ## Decimal/floating-point variables. global ENTRYxmin ENTRYxmax ENTRYymin ENTRYymax ENTRYfiltertol ## Integer variables. # global none? ## Error indicator. global EDITcode ## 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 ENTRYxmin [string trim $ENTRYxmin] set ENTRYxmax [string trim $ENTRYxmax] set ENTRYymin [string trim $ENTRYymin] set ENTRYymax [string trim $ENTRYymax] set ENTRYfiltertol [string trim $ENTRYfiltertol] ######################################################################### ## Check that these entry fields are NOT blank. ######################################################################### set MSGblank "is blank. Must NOT be blank." if {"$ENTRYxmin" == ""} { popup_msgVarWithScroll .topErr "The X-MIN parameter $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYxmax" == ""} { popup_msgVarWithScroll .topErr "The X-MAX parameter $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYymin" == ""} { popup_msgVarWithScroll .topErr "The Y-MIN parameter $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYymax" == ""} { popup_msgVarWithScroll .topErr "The Y-MAX parameter $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYfiltertol" == ""} { popup_msgVarWithScroll .topErr "The 'filter distance' parameter $MSGblank" +10+10 set EDITcode 1 return } ############################################################# ## Check that ENTRY variable ENTRY??? is an integer. ## COMMENTED, NOT USED, for now. ############################################################# if {0} { set MSGnotInteger " is NOT INTEGER." if {![string is integer -strict "$ENTRY???"]} { popup_msgVarWithScroll .topErr "The size of the '???' entry $MSGnotInteger" +10+10 set EDITcode 1 return } } ## END OF if {0} ######################################################################### ## Check that ENTRYxmin ENTRYxmax ENTRYymin ENTRYymin ENTRYfiltertol ## 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 "$ENTRYxmin"]} { popup_msgVarWithScroll .topErr "The X-MIN parameter $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYxmax"]} { popup_msgVarWithScroll .topErr "The X-MAX parameter $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYymin"]} { popup_msgVarWithScroll .topErr "The Y-MIN parameter $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYymax"]} { popup_msgVarWithScroll .topErr "The Y-MAX parameter $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYfiltertol"]} { popup_msgVarWithScroll .topErr "The 'Filter-Distance' parameter $NUMERICmsg" +10+10 set EDITcode 1 return } ####################################################################### ## Check that ENTRYfiltertol and ENTRYimgMaxSizePx are not negative. ####################################################################### set POSITIVEmsg "should be a POSITIVE number." if {$ENTRYfiltertol < 0.0} { popup_msgVarWithScroll .topErr "The 'filter distance' value $POSITIVEmsg" +10+10 set EDITcode 1 return } } ## END OF PROC 'edit_inputs' ##+######################################################################## ## PROC: 'decimal_check' ##+######################################################################## ## PURPOSE: Returns 1 or 0 if the input string looks like a decimal number ## --- positive or negative. Example numbers that are OK: ## 1.234 12.34 0.234 .234 6 ## -1.234 -12.34 -0.234 -.234 -6 ##+######################################################################## ## References (lots of one-liners): ## http://stackoverflow.com/questions/2072222/regular-expression-for-positive-and-a-negative-decimal-value-in-java ## http://stackoverflow.com/questions/308122/simple-regular-expression-for-a-decimal-with-a-precision-of-2 ## http://stackoverflow.com/questions/4246077/matching-numbers-with-regular-expressions-only-digits-and-commas/4247184#4247184 ## ## More specific to Tcl-Tk (including multi-liners): ## http://wiki.tcl.tk/989 'Regular Expression Examples' ## http://wiki.tcl.tk/768 'Entry Validation' - See "Integer forbidding leading zero:" ## http://wiki.tcl.tk/10166 'string is' ## http://wiki.tcl.tk/40710 'significant digits rounding' - uses regexp to split a number - ## Splits using: if {[regexp {^([+,-]?)([0-9]+)(\.?[0-9]*)?([eE][+-]?[0-9]+)?$} $num -> s i d e]} ## Removes leading zero with: regexp {^(0*)([1-9][0-9]*)$} $i -> NULL DIG ## http://wiki.tcl.tk/530 'Unit converter' has a regexp to parse numbers: ## set RE {(?ix) # Ignore case, extended syntax ## ([-+]?) # Optional leading sign ## ([0-9]*) # Integer part ## \.? # Optional decimal point ## ([0-9]*) # Fractional part ## (e?[0-9]*) # Optional exponent ## } ## ##+######################################################################## ## I do not mind incurring a little (minute amount of) processing ## with a multiple-line implementation. Probably easier to fix if ## a string gets through --- such as ".0.3" (two decimal points). ## ## CALLED BY: proc 'edit_inputs' ##+######################################################################## proc decimal_check {string} { set PosDecimalOK [regexp {^([0-9]*)\.?([0-9]*)$} "$string"] set NegDecimalOK [regexp {^\-([0-9]*)\.?([0-9]*)$} "$string"] set PosNakedDecimalOK [regexp {^\.?([0-9]*)$} "$string"] set NegNakedDecimalOK [regexp {^\-\.?([0-9]*)$} "$string"] set IntegerOK [string is integer $string] set retCODE [expr { \ $PosDecimalOK || $NegDecimalOK || \ $PosNakedDecimalOK || $NegNakedDecimalOK || \ $IntegerOK }] ## FOR TESTING: if {0} { puts "" puts "decimal_check:" puts "string: $string" puts "PosDecimalOK: $PosDecimalOK" puts "NegDecimalOK: $NegDecimalOK" puts "PosNakedDecimalOK: $PosNakedDecimalOK" puts "NegNakedDecimalOK: $NegNakedDecimalOK" puts "IntegerOK: $IntegerOK" puts "retCODE: $retCODE" } return $retCODE } ## END OF PROC 'decimal_check' ##+######################################################################## ## PROC: 'numeric_check_string_double' ##+######################################################################## ## PURPOSE: To check whether a number/string is a valid floating-point ## number by using the Tcl 'string is double' command. ## ## CALLED BY: proc 'read-file_write-file' ##+######################################################################## proc numeric_check_string_double {str2chk} { global EDITcode set EDITcode 0 if {[string is double -strict "$str2chk"] == 0} { popup_msgVarWithScroll .topErr "The string '$str2chk' is not a floating point number. Data error?" +10+10 set EDITcode 1 return } } ## END OF PROC 'numeric_check_string_double' ##+######################################################################## ## PROC: 'enable_disable_minmaxEntries' ##+######################################################################## ## PURPOSE: Disable or enable the Xmin,Xmax,Ymin,Ymax entry fields. ## ## CALLEC BY: a button1-release binding on the 'Filter' checkbutton ## and in the 'Additional GUI Initialization' section at ## the bottom of this Tk script. ##+######################################################################## proc enable_disable_minmaxEntries {} { global FILTERonLIMITS0or1 if {$FILTERonLIMITS0or1 == 0} { .fRlimits.entryXMINLIM configure -state disabled .fRlimits.entryXMAXLIM configure -state disabled .fRlimits.entryYMINLIM configure -state disabled .fRlimits.entryYMAXLIM configure -state disabled .fRlimits.labelXMINLIM configure -state disabled .fRlimits.labelXMAXLIM configure -state disabled .fRlimits.labelYMINLIM configure -state disabled .fRlimits.labelYMAXLIM configure -state disabled } else { .fRlimits.entryXMINLIM configure -state normal .fRlimits.entryXMAXLIM configure -state normal .fRlimits.entryYMINLIM configure -state normal .fRlimits.entryYMAXLIM configure -state normal .fRlimits.labelXMINLIM configure -state normal .fRlimits.labelXMAXLIM configure -state normal .fRlimits.labelYMINLIM configure -state normal .fRlimits.labelYMAXLIM configure -state normal } } ## END OF PROC 'enable_disable_minmaxEntries' ##+######################################################################## ## 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' ##+######################################################## ## Set the 'HELPtext' var. ##+######################################################## set HELPtext \ " ** HELP for this software application whose purpose is to ** ** 'Read an Outline Data File (containing 2 columns of numeric data, ** such as Longitude-Latitude data in decimal-degrees) --- and ** Write a new Outline Data File according to Filter Criteria' ** This Tk GUI script reads an 'outline' data file whose lines contain pairs of coordinates (example: longitude and latitude in decimal degrees) and writes a new 'outline' file by using a 'filter distance' value to skip writing any point data-line when that point is very close to the previously read point. Thus this utility can be used to generate a less-detailed outline file from an overly-detailed, unnecessarily-large outline file. NOTE: The outline data does not have to be map (longitude,latitude) data. It could be pairs of coordinates that provide an outline of almost any object --- for example a silouette of a person or an animal or an insect or a fish --- OR a logo or alphanumeric characters in different fonts --- OR geometric figures (like pentagons, hexagons, octagons, ...) --- OR a more complicated geometric figure could be defined by the points in the file --- such as a fractal-like geometric figure, like the 3rd or 4th level of a 'Koch Snowlake'. In fact, the outline data could depict a snowflake --- or flower petals --- or the outline of a leaf --- or the outline of tree branches--- or mosaic patterns. Use your imagination. In addition to using the 'filter distance' value (to reduce the number of records chosen from the input outline-data-file), this utility provides the option to filter according to Xmin, Xmax, Ymin, Ymax values that can be entered in 4 'entry' widgets on the GUI. A 'checkbutton' widget on the GUI determines whether this X-Y-limits filter is applied. NOTE: Coordinate-pair data in files for drawing maps is usually provided with LONGITUDE BEFORE LATITUDE. You can think of the LONGITUDE value as providing the X-distance along the equator of a planet, and the LATITUDE value as providing the Y-distance from the equator to a north or south pole. The LONGITUDE angle is usually specified between -180 and +180 degrees --- from a zero longitude (like the longitude line through Greenwich, England on Earth). The LATITUDE angle is usually specified between -90 and +90 degrees, measured from the equator. *********************** SOURCES OF OUTLINE DATA : *********************** The x,y data in the input file (which MAY be longitude,latitude data) is assumed to be in ASCII format, NOT binary. There are many country/continent/state/county/region/multi-country outline (boundary) data files on the internet in *ASCII* format. Examples: - 'GeoJSON' files References: https://en.wikipedia.org/wiki/JSON http://json.org https://en.wikipedia.org/wiki/GeoJSON http://geojson.org - 'KML' and 'KMZ' files (where KML = Keyhole Markup Language) which are used in Google Earth and various GPS devices. References: https://en.wikipedia.org/wiki/Keyhole_Markup_Language http://www.opengeospatial.org/standards/kml/ https://developers.google.com/kml/documentation/?csw=1 There is a lot of XML-like markup language in '.geojson' and and '.kml'/'.kmz' files. ('.kmz' files are compressed '.kml' files.) For input to this utility, the user can take those files and 'clean them up' so that there are only a pair of space-separated (or comma-separated) decimal numbers in each line. The input file can contain some comment lines indicated by '#' in column one of each comment line. Typically there may be one (or a few) comment lines at the top of the file that describe the contents of the file --- and that, optionally, document the source of the data. NOTE: Generally, in 2016, there are several times more '.kml' and '.kmz' files than '.geojson' files to be found on the internet by searching for files with those suffixes. In '.kml' files, the x,y data is between and markers. Eventually, someone could add to this utility the ability to read '.geojson' and '.kml' and '.kmz' files --- thus avoiding a need for editing them. BUT, for now, this utility is written to read input files in this very simple, minimal format --- x,y ASCII decimal number pairs --- without any XML-like markup language in the input file. There will be shell scripts in the 'FE Nautilus Scripts' subsystem that extract the x,y coordinate pairs from KML and GeoJSON files --- and eliminate most of the markup language. ****************************************** NOTES ON HOW THE DATA IS READ and FILTERED: ****************************************** We include, in the file-reading proc of this Tk script, the ability to replace any comma character with a space --- so that this utility can read CSV = Comma-Separated-Variable files without having to edit the file to remove the commas. In reading the input file for the first time, the min and max of the x and y values are determined and shown in the 'entry' widgets for Xmin,Xmax,Ymin,Ymax. The user can use the 'Filter-min-max' checkbutton to activate those four entry fields. Then narrow the min-max limits in order to reduce the number of point data lines in the output file. --- Note that some geographic information files --- such as country or region files --- may contain separate outline 'loops' --- typically representing islands or lakes in the country or region. Example countries: Argentina, Bahamas, Canada Those loops can be separated in the simple input files of this utility by separating the lines of x,y data by a blank line (or a '#' comment line) between the 'loops' of data. This Tk script writes those blank and comment (separator) lines, as they exist in the input file. ********************************** USING THE OUTPUT OUTLINE DATA FILE : ********************************** The 'filtered-outline' output file could be read into one of the other FE 'tkGooie' 'MAPtools' utilities --- namely, 'tkReadOutlineFile_drawOutlineImage.tk' --- to create a drawing of an outline --- typically a 'flattened' map from longitude-latitude data in decimal degrees. " ##+##################################################### ##+##################################################### ## ADDITIONAL GUI INITIALIZATION, if needed (or wanted). ##+##################################################### ##+##################################################### ##+##################################################### ## Set the full-name of the RGB color-selector Tk script ## that is used in several procs above. ##+##################################################### ## 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]" set CURdir "[pwd]" } ##+############################################################# ## Set an initial directory location CURdir for the 'get_filename' ## proc --- an initial directory in which to look for boundary-data ## filenames. ##+############################################################## ## For ease of testing in a Linux/Unix terminal whose 'current ## working directory' is located at the directory containing this ## Tk script (and a test data file), we set CURdir to 'pwd' if ## it looks like this script is being run in a terminal-testing ## mode. ## ## Otherwise, we set CURdir to the user home directory. ##+############################################################## set CURdir "$env(HOME)" if {"$DIRthisScript" == "[pwd]"} { set CURdir "[pwd]" } ##+############################################################## ## Set a directory for output, such as a Postscript plot file. ##+############################################################## set DIRtemp "/tmp" #+####################################################### ## Set a text viewer or editor for the output file. #+####################################################### # set TEXTviewer "$env(HOME)/apps/bin/xpg" # set TEXTeditor "/usr/bin/kate" # set TEXTeditor "/usr/bin/kedit" # set TEXTeditor "/usr/bin/gedit" set TEXTeditor "/usr/bin/scite" # set TEXTeditor "$env(HOME)/apps/bin/gscite" #+####################################################### ## Initialize the entry widgets on the GUI. ##+###################################################### reset_parms #+####################################################### ## Initialize the 'Filter-min-max' checkbutton and ## disable or enable the min-max entry widgets accordingly. ##+###################################################### set FILTERonLIMITS0or1 0 enable_disable_minmaxEntries ##+##################################################### ## Advise the user how to start. ##+##################################################### advise_user "$aRtext(STARTmsg)"