Skip to content
This repository has been archived by the owner on Dec 22, 2021. It is now read-only.

Commit

Permalink
Merge 39e5257 into b94f526
Browse files Browse the repository at this point in the history
  • Loading branch information
santisoler committed Nov 17, 2017
2 parents b94f526 + 39e5257 commit 3138a74
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 50 deletions.
139 changes: 90 additions & 49 deletions fatiando/gravmag/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ class Moulder(object):
epsilon = 5
# App instructions printed in the figure suptitle
instructions = ' | '.join([
'n: New polygon', 'd: delete', 'click: select/move', 'esc: cancel'])
'n: New polygon', 'd: delete', 'click: select/move', 'a: add vertex',
'r: reset view', 'esc: cancel'])

def __init__(self, area, x, z, data=None, density_range=[-2000, 2000],
**kwargs):
Expand Down Expand Up @@ -206,7 +207,7 @@ def model(self):
"""
The polygon model drawn as :class:`fatiando.mesher.Polygon` objects.
"""
m = [Polygon(p.xy, {'density': d})
m = [Polygon(p.xy, {'density': d}, force_clockwise=True)
for p, d in zip(self.polygons, self.densities)]
return m

Expand Down Expand Up @@ -237,6 +238,7 @@ def run(self):
self._ipoly = None
self._lastevent = None
self._drawing = False
self._add_vertex = False
self._xy = []
self._drawing_plot = None
# Used to blit the model plot and make
Expand All @@ -262,14 +264,6 @@ def _connect(self):
self._button_release_callback)
self.canvas.mpl_connect('motion_notify_event',
self._mouse_move_callback)
self.canvas.mpl_connect('draw_event',
self._draw_callback)
# Call the cleanup and extra code for a draw event when resizing as
# well. This is needed so that tight_layout adjusts the figure when
# resized. Otherwise, tight_layout snaps only when the user clicks on
# the figure to do something.
self.canvas.mpl_connect('resize_event',
self._draw_callback)
self.density_slider.on_changed(self._set_density_callback)
self.error_slider.on_changed(self._set_error_callback)

Expand Down Expand Up @@ -312,13 +306,16 @@ def _figure_setup(self, **kwargs):
The created figure
"""
sharex = kwargs.get('sharex')
if not sharex:
kwargs['sharex'] = True
fig, axes = pyplot.subplots(2, 1, **kwargs)
ax1, ax2 = axes
self.predicted_line, = ax1.plot(self.x, self.predicted, '-r')
if self.data is not None:
self.data_line, = ax1.plot(self.x, self.data, '.k')
ax1.set_ylabel('Gravity anomaly (mGal)')
ax1.set_xlabel('x (m)', labelpad=-10)
ax2.set_xlabel('x (m)', labelpad=-10)
ax1.set_xlim(self.area[:2])
ax1.set_ylim((-200, 200))
ax1.grid(True)
Expand Down Expand Up @@ -418,16 +415,6 @@ def _update_data_plot(self):
self.dataax.grid(True)
self.canvas.draw()

def _draw_callback(self, value):
"""
Callback for the canvas.draw() event.
This is called everytime the figure is redrawn. Used to do some
clean up and tunning whenever this is called as well, like calling
``tight_layout``.
"""
self.figure.tight_layout()

def _set_error_callback(self, value):
"""
Callback when error slider is edited
Expand Down Expand Up @@ -486,6 +473,27 @@ def _get_polygon_vertice_id(self, event):
v = [0, last]
return p, v

def _add_new_vertex(self, event):
"""
Add new vertex to polygon
"""
vertices = self.polygons[self._ipoly].get_xy()
x, y = vertices[:, 0], vertices[:, 1]
# Compute the angle between the vectors to each pair of
# vertices corresponding to each line segment of the polygon
x1, y1 = x[:-1], y[:-1]
x2, y2 = numpy.roll(x1, -1), numpy.roll(y1, -1)
u = numpy.vstack((x1 - event.xdata, y1 - event.ydata)).T
v = numpy.vstack((x2 - event.xdata, y2 - event.ydata)).T
angle = numpy.arccos(numpy.sum(u*v, 1) /
numpy.sqrt(numpy.sum(u**2, 1)) /
numpy.sqrt(numpy.sum(v**2, 1)))
position = angle.argmax() + 1
x = numpy.hstack((x[:position], event.xdata, x[position:]))
y = numpy.hstack((y[:position], event.ydata, y[position:]))
new_vertices = numpy.vstack((x, y)).T
return new_vertices

def _button_press_callback(self, event):
"""
What actions to perform when a mouse button is clicked
Expand All @@ -494,24 +502,46 @@ def _button_press_callback(self, event):
return
if event.button == 1 and not self._drawing and self.polygons:
self._lastevent = event
for line, poly in zip(self.lines, self.polygons):
poly.set_animated(False)
line.set_animated(False)
line.set_color([0, 0, 0, 0])
self.canvas.draw()
# Find out if a click happened on a vertice
# and which vertice of which polygon
self._ipoly, self._ivert = self._get_polygon_vertice_id(event)
if self._ipoly is not None:
self.density_slider.set_val(self.densities[self._ipoly])
self.polygons[self._ipoly].set_animated(True)
self.lines[self._ipoly].set_animated(True)
self.lines[self._ipoly].set_color([0, 1, 0, 0])
if not self._add_vertex:
for line, poly in zip(self.lines, self.polygons):
poly.set_animated(False)
line.set_animated(False)
line.set_color([0, 0, 0, 0])
self.canvas.draw()
self.background = self.canvas.copy_from_bbox(self.modelax.bbox)
self.modelax.draw_artist(self.polygons[self._ipoly])
self.modelax.draw_artist(self.lines[self._ipoly])
self.canvas.blit(self.modelax.bbox)
# Find out if a click happened on a vertice
# and which vertice of which polygon
self._ipoly, self._ivert = self._get_polygon_vertice_id(event)
if self._ipoly is not None:
self.density_slider.set_val(self.densities[self._ipoly])
self.polygons[self._ipoly].set_animated(True)
self.lines[self._ipoly].set_animated(True)
self.lines[self._ipoly].set_color([0, 1, 0, 0])
self.canvas.draw()
self.background = self.canvas.copy_from_bbox(
self.modelax.bbox)
self.modelax.draw_artist(self.polygons[self._ipoly])
self.modelax.draw_artist(self.lines[self._ipoly])
self.canvas.blit(self.modelax.bbox)
else:
# If a polygon is selected, we will add a new vertex by
# removing the polygon and inserting a new one with the extra
# vertex.
if self._ipoly is not None:
vertices = self._add_new_vertex(event)
density = self.densities[self._ipoly]
polygon, line = self._make_polygon(vertices, density)
self.polygons[self._ipoly].remove()
self.lines[self._ipoly].remove()
self.polygons.pop(self._ipoly)
self.lines.pop(self._ipoly)
self.polygons.insert(self._ipoly, polygon)
self.lines.insert(self._ipoly, line)
self.modelax.add_patch(polygon)
self.modelax.add_line(line)
self.lines[self._ipoly].set_color([0, 1, 0, 0])
self.canvas.draw()
self._update_data()
self._update_data_plot()
elif self._drawing:
if event.button == 1:
self._xy.append([event.xdata, event.ydata])
Expand Down Expand Up @@ -547,6 +577,8 @@ def _button_release_callback(self, event):
return
if event.button != 1:
return
if self._add_vertex:
self._add_vertex = False
if self._ivert is None and self._ipoly is None:
return
self.background = None
Expand All @@ -565,8 +597,6 @@ def _key_press_callback(self, event):
"""
What to do when a key is pressed on the keyboard.
"""
if event.inaxes is None:
return
if event.key == 'd':
if self._drawing and self._xy:
self._xy.pop()
Expand Down Expand Up @@ -621,16 +651,25 @@ def _key_press_callback(self, event):
'esc: cancel']))
self.canvas.draw()
elif event.key == 'escape':
self._drawing = False
self._xy = []
if self._drawing_plot is not None:
self._drawing_plot.remove()
self._drawing_plot = None
for line, poly in zip(self.lines, self.polygons):
poly.set_animated(False)
line.set_animated(False)
line.set_color([0, 0, 0, 0])
if self._add_vertex:
self._add_vertex = False
else:
self._drawing = False
self._xy = []
if self._drawing_plot is not None:
self._drawing_plot.remove()
self._drawing_plot = None
for line, poly in zip(self.lines, self.polygons):
poly.set_animated(False)
line.set_animated(False)
line.set_color([0, 0, 0, 0])
self.canvas.draw()
elif event.key == 'r':
self.modelax.set_xlim(self.area[:2])
self.modelax.set_ylim(self.area[2:])
self._update_data_plot()
elif event.key == 'a':
self._add_vertex = not self._add_vertex

def _mouse_move_callback(self, event):
"""
Expand All @@ -642,6 +681,8 @@ def _mouse_move_callback(self, event):
return
if self._ivert is None and self._ipoly is None:
return
if self._add_vertex:
return
x, y = event.xdata, event.ydata
p = self._ipoly
v = self._ivert
Expand Down
55 changes: 54 additions & 1 deletion fatiando/mesher/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ class Polygon(GeometricElement):
"""

def __init__(self, vertices, props=None):
def __init__(self, vertices, props=None, force_clockwise=False):
super().__init__(props)
self._vertices = np.asarray(vertices)
if force_clockwise:
self.set_clockwise()

@property
def vertices(self):
Expand All @@ -92,6 +94,57 @@ def x(self):
def y(self):
return self.vertices[:, 1]

@property
def orientation(self):
area = self.get_area(absolute=False)
if area < 0:
return "CW"
else:
return "CCW"

def set_clockwise(self):
self.set_orientation("CW")

def set_orientation(self, orientation):
if orientation not in ["CW", "CWW"]:
raise ValueError("Orientation must be 'CW' or 'CCW'")
current = self.orientation
if orientation != current:
self._vertices = self._vertices[::-1]

def get_area(self, absolute=True):
"""
Compute the area of the Polygon.
.. math::
A = \frac{1}{2} | \sum\limits_{i=0}^{n-1}
(x_{i} y_{i+1} - x_{i+1} y_{i}) |
where :math:`x_0 = x_n` and :math:`y_0 = y_n`.
If ``absolute`` is ``False`` the area is computed without applyting
the absolute value.
Parameters:
* absolute: bool
If True, the absolute value is returned, so the area is always
positive.
If False, the area can be either positive or negative.
The sign gives information about the orientation of the vertices:
If area is negative, the vertices are orientend clockwise.
if area is positive, the vertices are oriented counter-clockwise.
"""
vertices = self.vertices
x, y = vertices[:, 0], vertices[:, 1]
x = np.hstack((x[:], x[0]))
y = np.hstack((y[:], y[0]))
area = np.sum(x*np.roll(y, 1) - np.roll(x, 1)*y)
if absolute:
return np.abs(area)
else:
return area


class Square(Polygon):
"""
Expand Down

0 comments on commit 3138a74

Please sign in to comment.