00001
00002
00003 """
00004 Contains utilities for working with maps and field shapes.
00005 """
00006
00007 import itertools
00008 import numpy as np
00009 from PIL import Image
00010 from shapely.geometry import Polygon, LineString
00011 from matplotlib.collections import LineCollection
00012 from math import cos, sin, sqrt, atan, degrees, radians
00013
00014
00015 def image2array(im):
00016 if im.mode not in ("L", "F"):
00017 raise ValueError, "can only convert single-layer images"
00018 if im.mode == "L":
00019 a = np.fromstring(im.tostring(), dtype=np.uint8)
00020 else:
00021 a = np.fromstring(im.tostring(), dtype=np.float32)
00022 a.shape = im.size[1], im.size[0]
00023 return a
00024
00025 def array2image(a):
00026 if a.typecode() == np.uint8:
00027 mode = "L"
00028 elif a.typecode() == np.float32:
00029 mode = "F"
00030 else:
00031 raise ValueError, "unsupported image mode %s"%(a.typecode())
00032 return Image.fromstring(mode, (a.shape[1], a.shape[0]), a.tostring())
00033
00034
00035
00036 def ndarray2polygon(points):
00037 if type(points) != np.ndarray:
00038 raise TypeError("ndarray2polygon: takes an numpy.ndarray")
00039 new_tuples = []
00040 for point in points:
00041 new_tuples.append((float(point[0]),float(point[1])))
00042 return Polygon(new_tuples)
00043
00044
00045
00046 class RotationTransform:
00047 """Represents a rotational transform"""
00048 def __init__(self, angle):
00049 self.angle = angle
00050 self.w = radians(self.angle)
00051 self.irm = np.mat([[cos(self.w), -sin(self.w), 0.0],
00052 [sin(self.w), cos(self.w), 0.0],
00053 [0.0, 0.0, 1.0]])
00054
00055
00056 def rotation_tf_from_longest_edge(polygon):
00057 """Returns a rotation tf for the longest edge of the given polygon"""
00058 max_distance = None
00059 max_points = [(None, None),(None, None)]
00060
00061 xs, ys = polygon.exterior.xy
00062 points = zip(xs, ys)
00063 for i in range(len(points)):
00064 if i == len(points)-1:
00065 pair = (points[i],points[0])
00066 else:
00067 pair = (points[i],points[i+1])
00068 distance = sqrt((pair[1][0]-pair[0][0])**2+(pair[1][1]-pair[0][1])**2)
00069 if max_distance == None or max_distance < distance:
00070 max_distance = distance
00071 max_points = pair
00072
00073 dy = float(max_points[0][1] - max_points[1][1])
00074 dx = float(max_points[0][0] - max_points[1][0])
00075 return RotationTransform((degrees(atan(dy/dx))))
00076
00077 def rotate_polygon_to(polygon, rotation_transform):
00078 """Takes a polygon and a rotation, returns a rotated polygon"""
00079 points = np.array(polygon.exterior)
00080 tf_points = rotate_to(points, rotation_transform)
00081 return ndarray2polygon(tf_points)
00082
00083 def rotate_polygon_from(polygon, rotation_transform):
00084 """Takes a polygon and a rotation, returns an inverse rotated polygon"""
00085 points = np.array(polygon.exterior)
00086 tf_points = rotate_from(points, rotation_transform)
00087 return ndarray2polygon(tf_points)
00088
00089 def rotate_to(points, rotation_transform):
00090 """Rotates an ndarray of given points(x,y) to a given rotation"""
00091 if type(points) != np.ndarray:
00092 raise TypeError("rotate_to: takes an numpy.ndarray")
00093 new_points = []
00094 for point in points:
00095 point_mat = np.mat([[point[0]],[point[1]],[0]], dtype='float64')
00096 new_point = rotation_transform.irm * point_mat
00097 new_points.append(np.array(new_point[:-1].T, dtype='float64'))
00098 return np.squeeze(np.array(new_points, dtype='float64'))
00099
00100 def rotate_from(points, rotation_transform):
00101 """Rotate an ndarray of given points(x,y) from a given rotation"""
00102 if type(points) != np.ndarray:
00103 raise TypeError("rotate_from: takes an numpy.ndarray")
00104 new_points = []
00105 for point in points:
00106 point_mat = np.mat([[point[0]],[point[1]],[0]], dtype='float64')
00107 new_point = rotation_transform.irm.I * point_mat
00108 new_points.append(np.array(new_point[:-1].T, dtype='float64'))
00109 return np.squeeze(np.array(new_points, dtype='float64'))
00110
00111
00112
00113
00114 def zoom_extents(ax, polygons, buff=1.0):
00115 """Sets the axis to view all polygons given"""
00116 min_x = None
00117 max_x = None
00118 min_y = None
00119 max_y = None
00120 for polygon in polygons:
00121 bounds = polygon.bounds
00122 if min_x == None or bounds[0] < min_x:
00123 min_x = bounds[0]
00124 if min_y == None or bounds[1] < min_y:
00125 min_y = bounds[1]
00126 if max_x == None or bounds[2] > max_x:
00127 max_x = bounds[2]
00128 if max_y == None or bounds[3] > max_y:
00129 max_y = bounds[3]
00130 ax.set_xlim(min_x-buff,max_x+buff)
00131 ax.set_ylim(min_y-buff,max_y+buff)
00132 ax.set_aspect(1)
00133
00134 def make_axis():
00135 from pylab import plot, figure
00136 return figure(1, dpi=90).add_subplot(111), True
00137
00138 def plot_lines(lines, ax=None, color='#6699cc', alpha=1.0):
00139 import matplotlib.pyplot as pyplot
00140 show = False
00141 if ax == None:
00142 ax, show = make_axis()
00143 x, y = line.xy
00144 t = np.linspace(0, 10, len(x))
00145 lc = LineCollection(lines, cmap=pyplot.get_cmap('hot'),
00146 norm=pyplot.Normalize(0, 20))
00147 ax.add_collection(lc)
00148 lc.set_array(t)
00149 lc.set_linewidth(2)
00150 if show:
00151 import pylab; pylab.show()
00152
00153 def plot_line(line, ax=None, color='#6699cc', alpha=1.0):
00154 import matplotlib.pyplot as pyplot
00155 show = False
00156 if ax == None:
00157 ax, show = make_axis()
00158 x, y = line.xy
00159 lines = []
00160 for p in range(0, len(x), 2):
00161 lines.append(LineString([(x[p], y[p]), (x[p+1], y[p+1])]))
00162 t = np.linspace(12, 20, len(lines))
00163 lc = LineCollection(lines, cmap=pyplot.get_cmap('jet'),
00164 norm=pyplot.Normalize(0, 20))
00165 ax.add_collection(lc)
00166 lc.set_array(t)
00167 lc.set_linewidth(2)
00168 if show:
00169 import pylab; pylab.show()
00170
00171 def plot_coords(coords, ax=None, color='#999999', alpha=1.0):
00172 show = False
00173 if ax == None:
00174 ax, show = make_axis()
00175 x, y = coords.xy
00176 ax.plot(x, y, 'o', color=color, alpha=alpha, zorder=1)
00177 if show:
00178 import pylab; pylab.show()
00179
00180 def plot_polygon(polygon, ax=None, color='#999999', alpha=1.0):
00181 show = False
00182 if ax == None:
00183 ax, show = make_axis()
00184 x,y = polygon.exterior.xy
00185 ax.plot(x,y, color=color, alpha=alpha)
00186 ax.set_xlim(min(x)-1,max(x)+1)
00187 ax.set_ylim(min(y)-1,max(y)+1)
00188 ax.set_aspect(1)
00189 if show:
00190 import pylab; pylab.show()
00191
00192
00193
00194 if __name__ == '__main__':
00195 ext = [(1, 1), (-3, 11), (4, 16), (10, 10)]
00196 polygon = Polygon(ext)
00197 polygon_points = np.array(polygon.exterior)
00198
00199 rt = rotation_tf_from_longest_edge(polygon)
00200 print rt.angle
00201 print rt.w
00202
00203 tf_points = rotate_to(polygon_points, rt)
00204 tf_polygon = ndarray2polygon(tf_points)
00205
00206 ax, _ = make_axis()
00207 plot_polygon(polygon, ax, color='blue')
00208 plot_polygon(tf_polygon, ax, color='red')
00209 ax.axvline(x=0, color='black')
00210 ax.axhline(y=0, color='black')
00211 zoom_extents(ax, [polygon,tf_polygon])
00212 import pylab; pylab.show()
00213