123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949 |
- """
- @package animation.provider
- @brief Animation files and bitmaps management
- Classes:
- - mapwindow::BitmapProvider
- - mapwindow::BitmapRenderer
- - mapwindow::BitmapComposer
- - mapwindow::DictRefCounter
- - mapwindow::MapFilesPool
- - mapwindow::BitmapPool
- - mapwindow::CleanUp
- (C) 2013 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Anna Petrasova <kratochanna gmail.com>
- """
- import os
- import sys
- import wx
- import tempfile
- from multiprocessing import Process, Queue
- from core.gcmd import GException, DecodeString
- from core.settings import UserSettings
- from core.debug import Debug
- from core.utils import autoCropImageFromFile
- from animation.utils import HashCmd, HashCmds, GetFileFromCmd, GetFileFromCmds
- from gui_core.wrap import EmptyBitmap, BitmapFromImage
- import grass.script.core as gcore
- from grass.script.task import cmdlist_to_tuple
- from grass.pydispatch.signal import Signal
- class BitmapProvider:
- """Class for management of image files and bitmaps.
- There is one instance of this class in the application.
- It handles both 2D and 3D animations.
- """
- def __init__(
- self, bitmapPool, mapFilesPool, tempDir, imageWidth=640, imageHeight=480
- ):
- self._bitmapPool = bitmapPool
- self._mapFilesPool = mapFilesPool
- self.imageWidth = (
- imageWidth # width of the image to render with d.rast or d.vect
- )
- # height of the image to render with d.rast or d.vect
- self.imageHeight = imageHeight
- self._tempDir = tempDir
- self._uniqueCmds = []
- self._cmdsForComposition = []
- self._opacities = []
- self._cmds3D = []
- self._regionFor3D = None
- self._regions = []
- self._regionsForUniqueCmds = []
- self._renderer = BitmapRenderer(
- self._mapFilesPool, self._tempDir, self.imageWidth, self.imageHeight
- )
- self._composer = BitmapComposer(
- self._tempDir,
- self._mapFilesPool,
- self._bitmapPool,
- self.imageWidth,
- self.imageHeight,
- )
- self.renderingStarted = Signal("BitmapProvider.renderingStarted")
- self.compositionStarted = Signal("BitmapProvider.compositionStarted")
- self.renderingContinues = Signal("BitmapProvider.renderingContinues")
- self.compositionContinues = Signal("BitmapProvider.compositionContinues")
- self.renderingFinished = Signal("BitmapProvider.renderingFinished")
- self.compositionFinished = Signal("BitmapProvider.compositionFinished")
- self.mapsLoaded = Signal("BitmapProvider.mapsLoaded")
- self._renderer.renderingContinues.connect(self.renderingContinues)
- self._composer.compositionContinues.connect(self.compositionContinues)
- def SetCmds(self, cmdsForComposition, opacities, regions=None):
- """Sets commands to be rendered with opacity levels.
- Applies to 2D mode.
- :param cmdsForComposition: list of lists of command lists
- [[['d.rast', 'map=elev_2001'], ['d.vect', 'map=points']], # g.pnmcomp
- [['d.rast', 'map=elev_2002'], ['d.vect', 'map=points']],
- ...]
- :param opacities: list of opacity values
- :param regions: list of regions
- """
- Debug.msg(
- 2, "BitmapProvider.SetCmds: {n} lists".format(n=len(cmdsForComposition))
- )
- self._cmdsForComposition.extend(cmdsForComposition)
- self._opacities.extend(opacities)
- self._regions.extend(regions)
- self._getUniqueCmds()
- def SetCmds3D(self, cmds, region):
- """Sets commands for 3D rendering.
- :param cmds: list of commands m.nviz.image (cmd as a list)
- :param region: for 3D rendering
- """
- Debug.msg(2, "BitmapProvider.SetCmds3D: {c} commands".format(c=len(cmds)))
- self._cmds3D = cmds
- self._regionFor3D = region
- def _getUniqueCmds(self):
- """Returns list of unique commands.
- Takes into account the region assigned."""
- unique = list()
- for cmdList, region in zip(self._cmdsForComposition, self._regions):
- for cmd in cmdList:
- if region:
- unique.append((tuple(cmd), tuple(sorted(region.items()))))
- else:
- unique.append((tuple(cmd), None))
- unique = list(set(unique))
- self._uniqueCmds = [cmdAndRegion[0] for cmdAndRegion in unique]
- self._regionsForUniqueCmds.extend(
- [
- dict(cmdAndRegion[1]) if cmdAndRegion[1] else None
- for cmdAndRegion in unique
- ]
- )
- def Unload(self):
- """Unloads currently loaded data.
- Needs to be called before setting new data.
- """
- Debug.msg(2, "BitmapProvider.Unload")
- if self._cmdsForComposition:
- for cmd, region in zip(self._uniqueCmds, self._regionsForUniqueCmds):
- del self._mapFilesPool[HashCmd(cmd, region)]
- for cmdList, region in zip(self._cmdsForComposition, self._regions):
- del self._bitmapPool[HashCmds(cmdList, region)]
- self._uniqueCmds = []
- self._cmdsForComposition = []
- self._opacities = []
- self._regions = []
- self._regionsForUniqueCmds = []
- if self._cmds3D:
- self._cmds3D = []
- self._regionFor3D = None
- def _dryRender(self, uniqueCmds, regions, force):
- """Determines how many files will be rendered.
- :param uniqueCmds: list of commands which are to be rendered
- :param force: if forced rerendering
- :param regions: list of regions assigned to the commands
- """
- count = 0
- for cmd, region in zip(uniqueCmds, regions):
- filename = GetFileFromCmd(self._tempDir, cmd, region)
- if (
- not force
- and os.path.exists(filename)
- and self._mapFilesPool.GetSize(HashCmd(cmd, region))
- == (self.imageWidth, self.imageHeight)
- ):
- continue
- count += 1
- Debug.msg(
- 3, "BitmapProvider._dryRender: {c} files to be rendered".format(c=count)
- )
- return count
- def _dryCompose(self, cmdLists, regions, force):
- """Determines how many lists of (commands) files
- will be composed (with g.pnmcomp).
- :param cmdLists: list of commands lists which are to be composed
- :param regions: list of regions assigned to the commands
- :param force: if forced rerendering
- """
- count = 0
- for cmdList, region in zip(cmdLists, regions):
- if (
- not force
- and HashCmds(cmdList, region) in self._bitmapPool
- and self._bitmapPool[HashCmds(cmdList, region)].GetSize()
- == (self.imageWidth, self.imageHeight)
- ):
- continue
- count += 1
- Debug.msg(
- 2, "BitmapProvider._dryCompose: {c} files to be composed".format(c=count)
- )
- return count
- def Load(self, force=False, bgcolor=(255, 255, 255), nprocs=4):
- """Loads data, both 2D and 3D. In case of 2D, it creates composites,
- even when there is only 1 layer to compose (to be changed for speedup)
- :param force: if True reload all data, otherwise only missing data
- :param bgcolor: background color as a tuple of 3 values 0 to 255
- :param nprocs: number of procs to be used for rendering
- """
- Debug.msg(
- 2,
- "BitmapProvider.Load: "
- "force={f}, bgcolor={b}, nprocs={n}".format(f=force, b=bgcolor, n=nprocs),
- )
- cmds = []
- regions = []
- if self._uniqueCmds:
- cmds.extend(self._uniqueCmds)
- regions.extend(self._regionsForUniqueCmds)
- if self._cmds3D:
- cmds.extend(self._cmds3D)
- regions.extend([None] * len(self._cmds3D))
- count = self._dryRender(cmds, regions, force=force)
- self.renderingStarted.emit(count=count)
- # create no data bitmap
- if None not in self._bitmapPool or force:
- self._bitmapPool[None] = createNoDataBitmap(
- self.imageWidth, self.imageHeight
- )
- ok = self._renderer.Render(
- cmds,
- regions,
- regionFor3D=self._regionFor3D,
- bgcolor=bgcolor,
- force=force,
- nprocs=nprocs,
- )
- self.renderingFinished.emit()
- if not ok:
- self.mapsLoaded.emit() # what to do here?
- return
- if self._cmdsForComposition:
- count = self._dryCompose(
- self._cmdsForComposition, self._regions, force=force
- )
- self.compositionStarted.emit(count=count)
- self._composer.Compose(
- self._cmdsForComposition,
- self._regions,
- self._opacities,
- bgcolor=bgcolor,
- force=force,
- nprocs=nprocs,
- )
- self.compositionFinished.emit()
- if self._cmds3D:
- for cmd in self._cmds3D:
- self._bitmapPool[HashCmds([cmd], None)] = wx.Bitmap(
- GetFileFromCmd(self._tempDir, cmd, None)
- )
- self.mapsLoaded.emit()
- def RequestStopRendering(self):
- """Requests to stop rendering/composition"""
- Debug.msg(2, "BitmapProvider.RequestStopRendering")
- self._renderer.RequestStopRendering()
- self._composer.RequestStopComposing()
- def GetBitmap(self, dataId):
- """Returns bitmap with given key
- or 'no data' bitmap if no such key exists.
- :param dataId: name of bitmap
- """
- try:
- bitmap = self._bitmapPool[dataId]
- except KeyError:
- bitmap = self._bitmapPool[None]
- return bitmap
- def WindowSizeChanged(self, width, height):
- """Sets size when size of related window changes."""
- Debug.msg(
- 5,
- "BitmapProvider.WindowSizeChanged: w={w}, h={h}".format(w=width, h=height),
- )
- self.imageWidth, self.imageHeight = width, height
- self._composer.imageWidth = self._renderer.imageWidth = width
- self._composer.imageHeight = self._renderer.imageHeight = height
- def LoadOverlay(self, cmd):
- """Creates raster legend with d.legend
- :param cmd: d.legend command as a list
- :return: bitmap with legend
- """
- Debug.msg(5, "BitmapProvider.LoadOverlay: cmd={c}".format(c=cmd))
- fileHandler, filename = tempfile.mkstemp(suffix=".png")
- os.close(fileHandler)
- # Set the environment variables for this process
- _setEnvironment(
- self.imageWidth,
- self.imageHeight,
- filename,
- transparent=True,
- bgcolor=(0, 0, 0),
- )
- Debug.msg(1, "Render raster legend " + str(filename))
- cmdTuple = cmdlist_to_tuple(cmd)
- returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
- if returncode == 0:
- return BitmapFromImage(autoCropImageFromFile(filename))
- else:
- os.remove(filename)
- raise GException(messages)
- class BitmapRenderer:
- """Class which renders 2D and 3D images to files."""
- def __init__(self, mapFilesPool, tempDir, imageWidth, imageHeight):
- self._mapFilesPool = mapFilesPool
- self._tempDir = tempDir
- self.imageWidth = imageWidth
- self.imageHeight = imageHeight
- self.renderingContinues = Signal("BitmapRenderer.renderingContinues")
- self._stopRendering = False
- self._isRendering = False
- def Render(self, cmdList, regions, regionFor3D, bgcolor, force, nprocs):
- """Renders all maps and stores files.
- :param cmdList: list of rendering commands to run
- :param regions: regions for 2D rendering assigned to commands
- :param regionFor3D: region for setting 3D view
- :param bgcolor: background color as a tuple of 3 values 0 to 255
- :param force: if True reload all data, otherwise only missing data
- :param nprocs: number of procs to be used for rendering
- """
- Debug.msg(3, "BitmapRenderer.Render")
- count = 0
- # Variables for parallel rendering
- proc_count = 0
- proc_list = []
- queue_list = []
- cmd_list = []
- filteredCmdList = []
- for cmd, region in zip(cmdList, regions):
- if cmd[0] == "m.nviz.image":
- region = None
- filename = GetFileFromCmd(self._tempDir, cmd, region)
- if (
- not force
- and os.path.exists(filename)
- and self._mapFilesPool.GetSize(HashCmd(cmd, region))
- == (self.imageWidth, self.imageHeight)
- ):
- # for reference counting
- self._mapFilesPool[HashCmd(cmd, region)] = filename
- continue
- filteredCmdList.append((cmd, region))
- mapNum = len(filteredCmdList)
- stopped = False
- self._isRendering = True
- for cmd, region in filteredCmdList:
- count += 1
- # Queue object for interprocess communication
- q = Queue()
- # The separate render process
- if cmd[0] == "m.nviz.image":
- p = Process(
- target=RenderProcess3D,
- args=(
- self.imageWidth,
- self.imageHeight,
- self._tempDir,
- cmd,
- regionFor3D,
- bgcolor,
- q,
- ),
- )
- else:
- p = Process(
- target=RenderProcess2D,
- args=(
- self.imageWidth,
- self.imageHeight,
- self._tempDir,
- cmd,
- region,
- bgcolor,
- q,
- ),
- )
- p.start()
- queue_list.append(q)
- proc_list.append(p)
- cmd_list.append((cmd, region))
- proc_count += 1
- # Wait for all running processes and read/store the created images
- if proc_count == nprocs or count == mapNum:
- for i in range(len(cmd_list)):
- proc_list[i].join()
- filename = queue_list[i].get()
- self._mapFilesPool[
- HashCmd(cmd_list[i][0], cmd_list[i][1])
- ] = filename
- self._mapFilesPool.SetSize(
- HashCmd(cmd_list[i][0], cmd_list[i][1]),
- (self.imageWidth, self.imageHeight),
- )
- proc_count = 0
- proc_list = []
- queue_list = []
- cmd_list = []
- self.renderingContinues.emit(current=count, text=_("Rendering map layers"))
- if self._stopRendering:
- self._stopRendering = False
- stopped = True
- break
- self._isRendering = False
- return not stopped
- def RequestStopRendering(self):
- """Requests to stop rendering."""
- if self._isRendering:
- self._stopRendering = True
- class BitmapComposer:
- """Class which handles the composition of image files with g.pnmcomp."""
- def __init__(self, tempDir, mapFilesPool, bitmapPool, imageWidth, imageHeight):
- self._mapFilesPool = mapFilesPool
- self._bitmapPool = bitmapPool
- self._tempDir = tempDir
- self.imageWidth = imageWidth
- self.imageHeight = imageHeight
- self.compositionContinues = Signal("BitmapComposer.composingContinues")
- self._stopComposing = False
- self._isComposing = False
- def Compose(self, cmdLists, regions, opacityList, bgcolor, force, nprocs):
- """Performs the composition of ppm/pgm files.
- :param cmdLists: lists of rendering commands lists to compose
- :param regions: regions for 2D rendering assigned to commands
- :param opacityList: list of lists of opacity values
- :param bgcolor: background color as a tuple of 3 values 0 to 255
- :param force: if True reload all data, otherwise only missing data
- :param nprocs: number of procs to be used for rendering
- """
- Debug.msg(3, "BitmapComposer.Compose")
- count = 0
- # Variables for parallel rendering
- proc_count = 0
- proc_list = []
- queue_list = []
- cmd_lists = []
- filteredCmdLists = []
- for cmdList, region in zip(cmdLists, regions):
- if (
- not force
- and HashCmds(cmdList, region) in self._bitmapPool
- and self._bitmapPool[HashCmds(cmdList, region)].GetSize()
- == (self.imageWidth, self.imageHeight)
- ):
- # TODO: find a better way than to assign the same to increase
- # the reference
- self._bitmapPool[HashCmds(cmdList, region)] = self._bitmapPool[
- HashCmds(cmdList, region)
- ]
- continue
- filteredCmdLists.append((cmdList, region))
- num = len(filteredCmdLists)
- self._isComposing = True
- for cmdList, region in filteredCmdLists:
- count += 1
- # Queue object for interprocess communication
- q = Queue()
- # The separate render process
- p = Process(
- target=CompositeProcess,
- args=(
- self.imageWidth,
- self.imageHeight,
- self._tempDir,
- cmdList,
- region,
- opacityList,
- bgcolor,
- q,
- ),
- )
- p.start()
- queue_list.append(q)
- proc_list.append(p)
- cmd_lists.append((cmdList, region))
- proc_count += 1
- # Wait for all running processes and read/store the created images
- if proc_count == nprocs or count == num:
- for i in range(len(cmd_lists)):
- proc_list[i].join()
- filename = queue_list[i].get()
- if filename is None:
- self._bitmapPool[
- HashCmds(cmd_lists[i][0], cmd_lists[i][1])
- ] = createNoDataBitmap(
- self.imageWidth, self.imageHeight, text="Failed to render"
- )
- else:
- self._bitmapPool[
- HashCmds(cmd_lists[i][0], cmd_lists[i][1])
- ] = BitmapFromImage(wx.Image(filename))
- os.remove(filename)
- proc_count = 0
- proc_list = []
- queue_list = []
- cmd_lists = []
- self.compositionContinues.emit(
- current=count, text=_("Overlaying map layers")
- )
- if self._stopComposing:
- self._stopComposing = False
- break
- self._isComposing = False
- def RequestStopComposing(self):
- """Requests to stop the composition."""
- if self._isComposing:
- self._stopComposing = True
- def RenderProcess2D(imageWidth, imageHeight, tempDir, cmd, region, bgcolor, fileQueue):
- """Render raster or vector files as ppm image and write the
- resulting ppm filename in the provided file queue
- :param imageWidth: image width
- :param imageHeight: image height
- :param tempDir: directory for rendering
- :param cmd: d.rast/d.vect command as a list
- :param region: region as a dict or None
- :param bgcolor: background color as a tuple of 3 values 0 to 255
- :param fileQueue: the inter process communication queue
- storing the file name of the image
- """
- filename = GetFileFromCmd(tempDir, cmd, region)
- transparency = True
- # Set the environment variables for this process
- _setEnvironment(
- imageWidth, imageHeight, filename, transparent=transparency, bgcolor=bgcolor
- )
- if region:
- os.environ["GRASS_REGION"] = gcore.region_env(**region)
- cmdTuple = cmdlist_to_tuple(cmd)
- returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
- if returncode != 0:
- gcore.warning("Rendering failed:\n" + messages)
- fileQueue.put(None)
- if region:
- os.environ.pop("GRASS_REGION")
- os.remove(filename)
- return
- if region:
- os.environ.pop("GRASS_REGION")
- fileQueue.put(filename)
- def RenderProcess3D(imageWidth, imageHeight, tempDir, cmd, region, bgcolor, fileQueue):
- """Renders image with m.nviz.image and writes the
- resulting ppm filename in the provided file queue
- :param imageWidth: image width
- :param imageHeight: image height
- :param tempDir: directory for rendering
- :param cmd: m.nviz.image command as a list
- :param region: region as a dict
- :param bgcolor: background color as a tuple of 3 values 0 to 255
- :param fileQueue: the inter process communication queue
- storing the file name of the image
- """
- filename = GetFileFromCmd(tempDir, cmd, None)
- os.environ["GRASS_REGION"] = gcore.region_env(region3d=True, **region)
- Debug.msg(1, "Render image to file " + str(filename))
- cmdTuple = cmdlist_to_tuple(cmd)
- cmdTuple[1]["output"] = os.path.splitext(filename)[0]
- # set size
- cmdTuple[1]["size"] = "%d,%d" % (imageWidth, imageHeight)
- # set format
- cmdTuple[1]["format"] = "ppm"
- cmdTuple[1]["bgcolor"] = bgcolor = ":".join([str(part) for part in bgcolor])
- returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
- if returncode != 0:
- gcore.warning("Rendering failed:\n" + messages)
- fileQueue.put(None)
- os.environ.pop("GRASS_REGION")
- return
- os.environ.pop("GRASS_REGION")
- fileQueue.put(filename)
- def CompositeProcess(
- imageWidth, imageHeight, tempDir, cmdList, region, opacities, bgcolor, fileQueue
- ):
- """Performs the composition of image ppm files and writes the
- resulting ppm filename in the provided file queue
- :param imageWidth: image width
- :param imageHeight: image height
- :param tempDir: directory for rendering
- :param cmdList: list of d.rast/d.vect commands
- :param region: region as a dict or None
- :param opacites: list of opacities
- :param bgcolor: background color as a tuple of 3 values 0 to 255
- :param fileQueue: the inter process communication queue
- storing the file name of the image
- """
- maps = []
- masks = []
- for cmd in cmdList:
- maps.append(GetFileFromCmd(tempDir, cmd, region))
- masks.append(GetFileFromCmd(tempDir, cmd, region, "pgm"))
- filename = GetFileFromCmds(tempDir, cmdList, region)
- # Set the environment variables for this process
- _setEnvironment(
- imageWidth, imageHeight, filename, transparent=False, bgcolor=bgcolor
- )
- opacities = [str(op) for op in opacities]
- bgcolor = ":".join([str(part) for part in bgcolor])
- returncode, stdout, messages = read2_command(
- "g.pnmcomp",
- overwrite=True,
- input="%s" % ",".join(reversed(maps)),
- mask="%s" % ",".join(reversed(masks)),
- opacity="%s" % ",".join(reversed(opacities)),
- bgcolor=bgcolor,
- width=imageWidth,
- height=imageHeight,
- output=filename,
- )
- if returncode != 0:
- gcore.warning("Rendering composite failed:\n" + messages)
- fileQueue.put(None)
- os.remove(filename)
- return
- fileQueue.put(filename)
- class DictRefCounter:
- """Base class storing map files/bitmaps (emulates dictionary).
- Counts the references to know which files/bitmaps to delete.
- """
- def __init__(self):
- self.dictionary = {}
- self.referenceCount = {}
- def __getitem__(self, key):
- return self.dictionary[key]
- def __setitem__(self, key, value):
- self.dictionary[key] = value
- if key not in self.referenceCount:
- self.referenceCount[key] = 1
- else:
- self.referenceCount[key] += 1
- Debug.msg(5, "DictRefCounter.__setitem__: +1 for key {k}".format(k=key))
- def __contains__(self, key):
- return key in self.dictionary
- def __delitem__(self, key):
- self.referenceCount[key] -= 1
- Debug.msg(5, "DictRefCounter.__delitem__: -1 for key {k}".format(k=key))
- def keys(self):
- return self.dictionary.keys()
- def Clear(self):
- """Clears items which are not needed any more."""
- Debug.msg(4, "DictRefCounter.Clear")
- for key in self.dictionary.copy().keys():
- if key is not None:
- if self.referenceCount[key] <= 0:
- del self.dictionary[key]
- del self.referenceCount[key]
- class MapFilesPool(DictRefCounter):
- """Stores rendered images as files."""
- def __init__(self):
- DictRefCounter.__init__(self)
- self.size = {}
- def SetSize(self, key, size):
- self.size[key] = size
- def GetSize(self, key):
- return self.size[key]
- def Clear(self):
- """Removes files which are not needed anymore.
- Removes both ppm and pgm.
- """
- Debug.msg(4, "MapFilesPool.Clear")
- for key in list(self.dictionary.keys()):
- if self.referenceCount[key] <= 0:
- name, ext = os.path.splitext(self.dictionary[key])
- os.remove(self.dictionary[key])
- if ext == ".ppm":
- os.remove(name + ".pgm")
- del self.dictionary[key]
- del self.referenceCount[key]
- del self.size[key]
- class BitmapPool(DictRefCounter):
- """Class storing bitmaps (emulates dictionary)"""
- def __init__(self):
- DictRefCounter.__init__(self)
- class CleanUp:
- """Responsible for cleaning up the files."""
- def __init__(self, tempDir):
- self._tempDir = tempDir
- def __call__(self):
- import shutil
- if os.path.exists(self._tempDir):
- try:
- shutil.rmtree(self._tempDir)
- Debug.msg(5, "CleanUp: removed directory {t}".format(t=self._tempDir))
- except OSError:
- gcore.warning(_("Directory {t} not removed.").format(t=self._tempDir))
- def _setEnvironment(width, height, filename, transparent, bgcolor):
- """Sets environmental variables for 2D rendering.
- :param width: rendering width
- :param height: rendering height
- :param filename: file name
- :param transparent: use transparency
- :param bgcolor: background color as a tuple of 3 values 0 to 255
- """
- Debug.msg(
- 5,
- "_setEnvironment: width={w}, height={h}, "
- "filename={f}, transparent={t}, bgcolor={b}".format(
- w=width, h=height, f=filename, t=transparent, b=bgcolor
- ),
- )
- os.environ["GRASS_RENDER_WIDTH"] = str(width)
- os.environ["GRASS_RENDER_HEIGHT"] = str(height)
- driver = UserSettings.Get(group="display", key="driver", subkey="type")
- os.environ["GRASS_RENDER_IMMEDIATE"] = driver
- os.environ["GRASS_RENDER_BACKGROUNDCOLOR"] = "{r:02x}{g:02x}{b:02x}".format(
- r=bgcolor[0], g=bgcolor[1], b=bgcolor[2]
- )
- os.environ["GRASS_RENDER_TRUECOLOR"] = "TRUE"
- if transparent:
- os.environ["GRASS_RENDER_TRANSPARENT"] = "TRUE"
- else:
- os.environ["GRASS_RENDER_TRANSPARENT"] = "FALSE"
- os.environ["GRASS_RENDER_FILE"] = str(filename)
- def createNoDataBitmap(imageWidth, imageHeight, text="No data"):
- """Creates 'no data' bitmap.
- Used when requested bitmap is not available (loading data was not successful) or
- we want to show 'no data' bitmap.
- :param imageWidth: image width
- :param imageHeight: image height
- """
- Debug.msg(
- 4,
- "createNoDataBitmap: w={w}, h={h}, text={t}".format(
- w=imageWidth, h=imageHeight, t=text
- ),
- )
- bitmap = EmptyBitmap(imageWidth, imageHeight)
- dc = wx.MemoryDC()
- dc.SelectObject(bitmap)
- dc.Clear()
- text = _(text)
- dc.SetFont(
- wx.Font(
- pointSize=40,
- family=wx.FONTFAMILY_SCRIPT,
- style=wx.FONTSTYLE_NORMAL,
- weight=wx.FONTWEIGHT_BOLD,
- )
- )
- tw, th = dc.GetTextExtent(text)
- dc.DrawText(text, (imageWidth - tw) // 2, (imageHeight - th) // 2)
- dc.SelectObject(wx.NullBitmap)
- return bitmap
- def read2_command(*args, **kwargs):
- kwargs["stdout"] = gcore.PIPE
- kwargs["stderr"] = gcore.PIPE
- ps = gcore.start_command(*args, **kwargs)
- stdout, stderr = ps.communicate()
- return ps.returncode, DecodeString(stdout), DecodeString(stderr)
- def test():
- import shutil
- from core.layerlist import LayerList, Layer
- from animation.data import AnimLayer
- from animation.utils import layerListToCmdsMatrix
- import grass.temporal as tgis
- tgis.init()
- layerList = LayerList()
- layer = AnimLayer()
- layer.mapType = "strds"
- layer.name = "JR"
- layer.cmd = ["d.rast", "map=elev_2007_1m"]
- layerList.AddLayer(layer)
- layer = Layer()
- layer.mapType = "vector"
- layer.name = "buildings_2009_approx"
- layer.cmd = ["d.vect", "map=buildings_2009_approx", "color=grey"]
- layer.opacity = 50
- layerList.AddLayer(layer)
- bPool = BitmapPool()
- mapFilesPool = MapFilesPool()
- tempDir = "/tmp/test"
- if os.path.exists(tempDir):
- shutil.rmtree(tempDir)
- os.mkdir(tempDir)
- # comment this line to keep the directory after prgm ends
- # cleanUp = CleanUp(tempDir)
- # import atexit
- # atexit.register(cleanUp)
- prov = BitmapProvider(bPool, mapFilesPool, tempDir, imageWidth=640, imageHeight=480)
- prov.renderingStarted.connect(
- lambda count: sys.stdout.write("Total number of maps: {c}\n".format(c=count))
- )
- prov.renderingContinues.connect(
- lambda current, text: sys.stdout.write(
- "Current number: {c}\n".format(c=current)
- )
- )
- prov.compositionStarted.connect(
- lambda count: sys.stdout.write(
- "Composition: total number of maps: {c}\n".format(c=count)
- )
- )
- prov.compositionContinues.connect(
- lambda current, text: sys.stdout.write(
- "Composition: Current number: {c}\n".format(c=current)
- )
- )
- prov.mapsLoaded.connect(lambda: sys.stdout.write("Maps loading finished\n"))
- cmdMatrix = layerListToCmdsMatrix(layerList)
- prov.SetCmds(cmdMatrix, [layer.opacity for layer in layerList])
- app = wx.App()
- prov.Load(bgcolor=(13, 156, 230), nprocs=4)
- for key in bPool.keys():
- if key is not None:
- bPool[key].SaveFile(os.path.join(tempDir, key + ".png"), wx.BITMAP_TYPE_PNG)
- # prov.Unload()
- # prov.SetCmds(cmdMatrix, [l.opacity for l in layerList])
- # prov.Load(bgcolor=(13, 156, 230))
- # prov.Unload()
- # newList = LayerList()
- # prov.SetCmds(layerListToCmdsMatrix(newList), [l.opacity for l in newList])
- # prov.Load()
- # prov.Unload()
- # mapFilesPool.Clear()
- # bPool.Clear()
- # print bPool.keys(), mapFilesPool.keys()
- if __name__ == "__main__":
- test()
|