"""
Module contains the ViewerApplication class
"""
# This file is part of 'TuiView' - a simple Raster viewer
# Copyright (C) 2012 Sam Gillingham
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
import argparse
from PyQt5.QtWidgets import QApplication, QMessageBox
from . import archivereader
from . import geolinkedviewers
from . import viewerstretch
from .viewerstrings import MESSAGE_TITLE
# True if we can't print to stderr etc
# (because we have been started in "GUI" mode on Windows)
GUI_MODE = sys.stderr is None
[docs]def showMessageAndExit(msg):
"""
If in gui mode, show message box with error and exit with 1
Otherwise raise SystemExit with msg
"""
if GUI_MODE:
QMessageBox.critical(None, MESSAGE_TITLE, msg)
sys.exit(1)
else:
raise SystemExit(msg)
[docs]class TuiViewArgumentParser(argparse.ArgumentParser):
"""
Same as argparse.ArgumentParser but overrides exit() and error()
so we can show a message box if required.
"""
def __init__(self):
super().__init__(add_help=False) # we do the help ourselves
[docs] def exit(self, status=0, message=None):
if message is not None:
if GUI_MODE:
QMessageBox.critical(None, MESSAGE_TITLE, message)
else:
print(message, file=sys.stderr)
sys.exit(status)
[docs] def error(self, message):
if GUI_MODE:
QMessageBox.critical(None, MESSAGE_TITLE, message)
else:
print(message, file=sys.stderr)
sys.exit(2)
[docs]def getCmdargs():
"""
Get commandline arguments
"""
p = TuiViewArgumentParser()
p.add_argument('-b', '--bands',
help="comma seperated list of bands to display")
p.add_argument('-c', '--colortable', action="store_true", default=False,
help="Apply color table to image")
p.add_argument('-g', '--greyscale', action="store_true", default=False,
help="Display image in greyscale")
p.add_argument('-r', '--rgb', action="store_true", default=False,
help="use 3 bands to create RGB image")
p.add_argument('-p', '--pseudocolor', nargs=1, metavar=('name',),
help="Display image using a pseudocolor ramp")
p.add_argument('-n', '--nostretch', action="store_true", default=False,
help="do no stretch on data")
p.add_argument('-l', '--linear', nargs=2, metavar=('minVal', 'maxVal'),
help="do a linear stretch between two values " +
"(eg '-l 0 10'). Pass 'stats' for statistics")
p.add_argument('-s', '--stddev', action="store_true", default=False,
help="do a 2 standard deviation stretch")
p.add_argument('--hist', action="store_true", default=False,
help="do a histogram stretch")
p.add_argument('--stretchfromtext',
help="Load stretch and lookup table from text file")
p.add_argument('--stretchfromgdal',
help="Load stretch and lookup table from GDAL file" +
" that contains saved stretch and lookup table")
p.add_argument('--noplugins', action="store_false", default=True,
dest='loadplugins', help="Don't load plugins")
p.add_argument('--separate', action="store_true", default=False,
help="load multiple files into separate windows")
p.add_argument('--goto', help="Zoom to a location. Format is:"+
" 'easting,northing,factor' where factor is meters"+
" per window pixel.")
p.add_argument('-v', '--vector', action='append', dest="vectors",
help="overlay vector file on top of all rasters." +
" Can be specified multiple times")
p.add_argument('--vectorlayer', action='append', dest="vectorlayers",
help="vector layer name(s) to use with --vector. " +
"Can't be specified if --vectorsql is used. " +
"Can be specified multiple times - once for each vector")
p.add_argument('--vectorsql', action='append', dest="vectorsqls",
help="vector SQL statement(s) to use with --vector. " +
"Can't be specified if --vectorlayer is used. " +
"Can be specified multiple times - once for each vector")
p.add_argument('--vectorlabel', action='append', dest="vectorlabels",
help="Vector attributes to label. Can be specified multiple times " +
"- once for each vector")
p.add_argument('-t', '--savedstate',
help="path to a .tuiview file with saved viewers state")
# do this ourselves so we can show QMessageBox if GUI_MODE
p.add_argument('-h', '--help', action="store_true", default=False,
help="Show this message and exit")
p.add_argument('filenames', nargs='*')
cmdargs = p.parse_args()
if cmdargs.help:
if GUI_MODE:
msgBox = QMessageBox(QMessageBox.Information, MESSAGE_TITLE,
"Get command line information by clicking 'Show Details...'",
QMessageBox.Ok)
msgBox.setDetailedText(p.format_help())
msgBox.exec_()
else:
p.print_help()
sys.exit(1)
# default values for these 'fake' parameters that we
# then set depending on the flags.
cmdargs.stretch = viewerstretch.ViewerStretch()
cmdargs.modeSet = False
cmdargs.stretchModeSet = False
cmdargs.bandsSet = False
if cmdargs.colortable:
cmdargs.stretch.setColorTable()
cmdargs.modeSet = True
if cmdargs.greyscale:
cmdargs.stretch.setGreyScale()
cmdargs.modeSet = True
if cmdargs.rgb:
cmdargs.stretch.setRGB()
cmdargs.modeSet = True
if cmdargs.pseudocolor is not None:
ramp = cmdargs.pseudocolor[0]
cmdargs.stretch.setPseudoColor(ramp)
cmdargs.modeSet = True
if cmdargs.nostretch:
cmdargs.stretch.setNoStretch()
cmdargs.stretchModeSet = True
if cmdargs.linear is not None:
(minVal, maxVal) = cmdargs.linear
try:
if minVal == 'stats':
minVal = None
else:
minVal = float(minVal)
if maxVal == 'stats':
maxVal = None
else:
maxVal = float(maxVal)
except ValueError as e:
showMessageAndExit(str(e))
cmdargs.stretch.setLinearStretch(minVal, maxVal)
cmdargs.stretchModeSet = True
if cmdargs.stddev:
cmdargs.stretch.setStdDevStretch()
cmdargs.stretchModeSet = True
if cmdargs.hist:
cmdargs.stretch.setHistStretch()
cmdargs.stretchModeSet = True
if cmdargs.bands is not None:
try:
bandlist = [int(x) for x in cmdargs.bands.split(',')]
except ValueError as e:
showMessageAndExit(str(e))
cmdargs.stretch.setBands(bandlist)
cmdargs.bandsSet = True
if cmdargs.stretchfromtext is not None:
try:
cmdargs.stretch = viewerstretch.ViewerStretch.fromTextFileWithLUT(
cmdargs.stretchfromtext)
cmdargs.modeSet = True
cmdargs.stretchModeSet = True
cmdargs.bandsSet = True
except Exception as e:
showMessageAndExit(str(e))
if cmdargs.stretchfromgdal is not None:
try:
cmdargs.stretch = viewerstretch.ViewerStretch.fromGDALFileWithLUT(
cmdargs.stretchfromgdal)
cmdargs.modeSet = True
cmdargs.stretchModeSet = True
cmdargs.bandsSet = True
except Exception as e:
showMessageAndExit(str(e))
return cmdargs
[docs]class ViewerApplication(QApplication):
"""
Main class for application
"""
def __init__(self):
QApplication.__init__(self, sys.argv)
self.pluginHandlers = []
# for settings
self.setApplicationName('tuiview')
self.setOrganizationName('TuiView')
cmdargs = getCmdargs()
loadplugins = cmdargs.loadplugins
self.viewers = geolinkedviewers.GeolinkedViewers(loadplugins)
stretch = None
if (cmdargs.modeSet and cmdargs.stretchModeSet and
cmdargs.bandsSet):
# use the stretch they have constructed
stretch = cmdargs.stretch
elif (cmdargs.modeSet or cmdargs.stretchModeSet or
cmdargs.bandsSet):
msg = ('Stretch incomplete. Must specify one of [-c|-g|-r] and' +
' one of [-n|-l|-s|--hist] and -b, or none to use defaults.')
showMessageAndExit(msg)
if cmdargs.vectorlayers is not None and cmdargs.vectorsqls is not None:
msg = 'Specify only one of --vectorlayer and --vectorsql'
showMessageAndExit(msg)
if (cmdargs.vectors is not None and cmdargs.vectorlayers is not None and
len(cmdargs.vectors) != len(cmdargs.vectorlayers)):
msg = 'If specified, you must pass one --vectorlayer per --vector'
showMessageAndExit(msg)
if (cmdargs.vectors is not None and cmdargs.vectorsqls is not None and
len(cmdargs.vectors) != len(cmdargs.vectorsqls)):
msg = 'If specified, you must pass one --vectorsql per --vector'
showMessageAndExit(msg)
if (cmdargs.vectors is not None and cmdargs.vectorlabels is not None and
len(cmdargs.vectors) != len(cmdargs.vectorlabels)):
msg = 'If specified, you must pass one --vectorlabel per --vector'
showMessageAndExit(msg)
if cmdargs.vectorlayers is not None and cmdargs.vectors is None:
msg = 'When specifying --vectorlayer you must also specify --vector'
showMessageAndExit(msg)
if cmdargs.vectorsqls is not None and cmdargs.vectors is None:
msg = 'When specifying --vectorsql you must also specify --vector'
showMessageAndExit(msg)
if cmdargs.vectorlabels is not None and (cmdargs.vectors is None or
(cmdargs.vectorlayers is None and cmdargs.vectorsqls is None)):
msg = ('When specifying --vectorlabel you must also specify '
'--vector and one of --vectorlayer or --vectorsql')
showMessageAndExit(msg)
if len(cmdargs.filenames) == 0 and cmdargs.savedstate is None:
self.viewers.newViewer()
else:
if cmdargs.separate:
# need to be in separate windows
for filename in cmdargs.filenames:
self.viewers.newViewer(filename, stretch)
else:
# load into one viewer
viewer = None
for filename in archivereader.file_list_to_archive_strings(
cmdargs.filenames):
if viewer is None:
viewer = self.viewers.newViewer(filename, stretch)
else:
viewer.addRasterInternal(filename, stretch)
# saved state
if cmdargs.savedstate is not None:
try:
fileobj = open(cmdargs.savedstate)
self.viewers.readViewersState(fileobj)
fileobj.close()
except Exception as e:
showMessageAndExit(str(e))
# open vectors in all viewer windows
if cmdargs.vectors is not None:
layername = None # reset if cmdargs.vectorsqls/cmdargs.vectorlayer exists
# otherwise carries the first one selected through to all the viewers
sql = None # not used if cmdargs.vectorsqls/cmdargs.vectorlayer exists, otherwise
# carries first one through to all the viewers
userCancel = False
for viewer in self.viewers.viewers:
for idx, vector in enumerate(cmdargs.vectors):
if cmdargs.vectorlayers is not None:
layername = cmdargs.vectorlayers[idx]
elif cmdargs.vectorsqls is not None:
sql = cmdargs.vectorsqls[idx]
label = None
if cmdargs.vectorlabels is not None:
label = cmdargs.vectorlabels[idx]
layername, sql = viewer.addVectorInternal(vector,
layername=layername, sql=sql, label=label)
if layername is None and sql is None:
# they canceled... break out of loop
userCancel = True
break
if userCancel:
break
# goto a location
if cmdargs.goto is not None:
from tuiview.viewerwidget import GeolinkInfo
arr = cmdargs.goto.split(',')
if len(arr) != 3:
msg = "goto usage: 'easting,northing,factor'"
showMessageAndExit(msg)
(easting, northing, metresperimgpix) = arr
easting = float(easting)
northing = float(northing)
metresperimgpix = float(metresperimgpix)
obj = GeolinkInfo(0, easting, northing, metresperimgpix)
self.viewers.onMove(obj)
[docs] def savePluginHandler(self, handler):
"""
Plugins need to be able to save an instance of their signal handling
object so it doesn't get cleaned up by Python's garbage collection.
There are a number of ways to address this, but this seems the cleanest.
Get an instance of this ViewerApplication class in the plugin by using the:
app = QApplication.instance()
Call.
"""
self.pluginHandlers.append(handler)
[docs]def run():
"""
Call this function to instantiate an instance of ViewerApplication
and have the command line parameters inspected etc and app run
"""
app = ViewerApplication()
app.exec_()