123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- # -*- coding: utf-8 -*-
- """!
- @package mapwin.analysis
- @brief Map display controllers for analyses (profiling, measuring)
- Classes:
- - analysis::AnalysisControllerBase
- - analysis::ProfileController
- - analysis::MeasureDistanceController
- (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 math
- import wx
- from core.utils import _
- import core.units as units
- from core.giface import Notification
- from grass.pydispatch.signal import Signal
- class AnalysisControllerBase:
- """!Base class for analysis which require drawing line in map display."""
- def __init__(self, giface, mapWindow):
- """!
- @param giface grass interface
- @param mapWindow instance of BufferedMapWindow
- """
- self._giface = giface
- self._mapWindow = mapWindow
- self._registeredGraphics = None
- self._oldMouseUse = None
- self._oldCursor = None
- def IsActive(self):
- """!Returns True if analysis mode is activated."""
- return bool(self._registeredGraphics)
- def _start(self, x, y):
- """!Handles the actual start of drawing line
- and adding each new point.
- @param x,y east north coordinates
- """
- if not self._registeredGraphics.GetAllItems():
- item = self._registeredGraphics.AddItem(coords=[[x, y]])
- item.SetPropertyVal('penName', 'analysisPen')
- else:
- # needed to switch mouse begin and end to draw intermediate line properly
- coords = self._registeredGraphics.GetItem(0).GetCoords()[-1]
- self._mapWindow.mouse['begin'] = self._mapWindow.Cell2Pixel(coords)
- def _addPoint(self, x, y):
- """!New point added.
- @param x,y east north coordinates
- """
- # add new point and calculate distance
- item = self._registeredGraphics.GetItem(0)
- coords = item.GetCoords() + [[x, y]]
- item.SetCoords(coords)
- # draw
- self._mapWindow.ClearLines()
- self._registeredGraphics.Draw(pdc=self._mapWindow.pdcTmp)
- wx.Yield()
- self._doAnalysis(coords)
- def _doAnalysis(self, coords):
- """!Perform the required analysis
- (compute distnace, update profile)
- @param coords EN coordinates
- """
- raise NotImplementedError()
- def _disconnectAll(self):
- """!Disconnect all mouse signals
- to stop drawing."""
- raise NotImplementedError()
- def _connectAll(self):
- """!Connect all mouse signals to draw."""
- raise NotImplementedError()
- def _getPen(self):
- """!Returns wx.Pen instance."""
- raise NotImplementedError()
- def Stop(self, restore=True):
- """!Analysis mode is stopped.
- @param restore if restore previous cursor, mouse['use']
- """
- self._mapWindow.ClearLines(pdc=self._mapWindow.pdcTmp)
- self._mapWindow.mouse['end'] = self._mapWindow.mouse['begin']
- # disconnect mouse events
- self._disconnectAll()
- # unregister
- self._mapWindow.UnregisterGraphicsToDraw(self._registeredGraphics)
- self._registeredGraphics = None
- self._mapWindow.Refresh()
- if restore:
- # restore mouse['use'] and cursor to the state before measuring starts
- self._mapWindow.SetNamedCursor(self._oldCursor)
- self._mapWindow.mouse['use'] = self._oldMouseUse
- def Start(self):
- """!Init analysis: register graphics to map window,
- connect required mouse signals.
- """
- self._oldMouseUse = self._mapWindow.mouse['use']
- self._oldCursor = self._mapWindow.GetNamedCursor()
- self._registeredGraphics = self._mapWindow.RegisterGraphicsToDraw(graphicsType='line')
- self._connectAll()
- # change mouse['box'] and pen to draw line during dragging
- # TODO: better solution for drawing this line
- self._mapWindow.mouse['use'] = None
- self._mapWindow.mouse['box'] = "line"
- self._mapWindow.pen = wx.Pen(colour='red', width=2, style=wx.SHORT_DASH)
- self._registeredGraphics.AddPen('analysisPen', self._getPen())
- # change the cursor
- self._mapWindow.SetNamedCursor('pencil')
- class ProfileController(AnalysisControllerBase):
- """!Class controls profiling in map display.
- It should be used inside ProfileFrame
- """
- def __init__(self, giface, mapWindow):
- AnalysisControllerBase.__init__(self, giface=giface, mapWindow=mapWindow)
- self.transectChanged = Signal('ProfileController.transectChanged')
- def _doAnalysis(self, coords):
- """!Informs profile dialog that profile changed.
- @param coords EN coordinates
- """
- self.transectChanged.emit(coords=coords)
- def _disconnectAll(self):
- self._mapWindow.mouseLeftDown.disconnect(self._start)
- self._mapWindow.mouseLeftUp.disconnect(self._addPoint)
- def _connectAll(self):
- self._mapWindow.mouseLeftDown.connect(self._start)
- self._mapWindow.mouseLeftUp.connect(self._addPoint)
- def _getPen(self):
- return wx.Pen(colour=wx.Colour(0, 100, 0), width=2, style=wx.SHORT_DASH)
- def Stop(self, restore=True):
- AnalysisControllerBase.Stop(self, restore=restore)
- self.transectChanged.emit(coords=[])
- class MeasureDistanceController(AnalysisControllerBase):
- """!Class controls measuring distance in map display."""
- def __init__(self, giface, mapWindow):
- AnalysisControllerBase.__init__(self, giface=giface, mapWindow=mapWindow)
- self._projInfo = self._mapWindow.Map.projinfo
- self._totaldist = 0.0 # total measured distance
- self._useCtypes = False
- def _doAnalysis(self, coords):
- """!New point added.
- @param x,y east north coordinates
- """
- self.MeasureDist(coords[-2], coords[-1])
- def _disconnectAll(self):
- self._mapWindow.mouseLeftDown.disconnect(self._start)
- self._mapWindow.mouseLeftUp.disconnect(self._addPoint)
- self._mapWindow.mouseDClick.disconnect(self.Stop)
- def _connectAll(self):
- self._mapWindow.mouseLeftDown.connect(self._start)
- self._mapWindow.mouseLeftUp.connect(self._addPoint)
- self._mapWindow.mouseDClick.connect(self.Stop)
- def _getPen(self):
- return wx.Pen(colour='green', width=2, style=wx.SHORT_DASH)
- def Stop(self, restore=True):
- AnalysisControllerBase.Stop(self, restore=restore)
- self._giface.WriteCmdLog(_('Measuring finished'))
- def Start(self):
- """!Init measurement routine that calculates map distance
- along transect drawn on map display
- """
- AnalysisControllerBase.Start(self)
- self._totaldist = 0.0 # total measured distance
- # initiating output (and write a message)
- # e.g., in Layer Manager switch to output console
- # TODO: this should be something like: write important message or write tip
- # TODO: mixed 'switching' and message? no, measuring handles 'swithing' on its own
- self._giface.WriteWarning(_('Click and drag with left mouse button '
- 'to measure.%s'
- 'Double click with left button to clear.') % \
- (os.linesep))
- if self._projInfo['proj'] != 'xy':
- mapunits = self._projInfo['units']
- self._giface.WriteCmdLog(_('Measuring distance') + ' ('
- + mapunits + '):')
- else:
- self._giface.WriteCmdLog(_('Measuring distance:'))
- if self._projInfo['proj'] == 'll':
- try:
- import grass.lib.gis as gislib
- gislib.G_begin_distance_calculations()
- self._useCtypes = True
- except ImportError, e:
- self._giface.WriteWarning(_('Geodesic distance calculation '
- 'is not available.\n'
- 'Reason: %s' % e))
- def MeasureDist(self, beginpt, endpt):
- """!Calculate distance and print to output window.
- @param beginpt,endpt EN coordinates
- """
- # move also Distance method?
- dist, (north, east) = self._mapWindow.Distance(beginpt, endpt, screen=False)
- dist = round(dist, 3)
- mapunits = self._projInfo['units']
- if mapunits == 'degrees' and self._useCtypes:
- mapunits = 'meters'
- d, dunits = units.formatDist(dist, mapunits)
- self._totaldist += dist
- td, tdunits = units.formatDist(self._totaldist,
- mapunits)
- strdist = str(d)
- strtotdist = str(td)
- if self._projInfo['proj'] == 'xy' or 'degree' not in self._projInfo['unit']:
- angle = int(math.degrees(math.atan2(north, east)) + 0.5)
- # uncomment below (or flip order of atan2(y,x) above) to use
- # the mathematical theta convention (CCW from +x axis)
- #angle = 90 - angle
- if angle < 0:
- angle = 360 + angle
- mstring = '%s = %s %s\n%s = %s %s\n%s = %d %s\n%s' \
- % (_('segment'), strdist, dunits,
- _('total distance'), strtotdist, tdunits,
- _('bearing'), angle, _('degrees (clockwise from grid-north)'),
- '-' * 60)
- else:
- mstring = '%s = %s %s\n%s = %s %s\n%s' \
- % (_('segment'), strdist, dunits,
- _('total distance'), strtotdist, tdunits,
- '-' * 60)
- self._giface.WriteLog(mstring, notification=Notification.MAKE_VISIBLE)
- return dist
|