1
2 __version__ = '$Revision: 8167 $'.split()[1]
3 __date__ = '$Date: 2008-01-09 14:44:36 -0500 (Wed, 09 Jan 2008) $'.split()[1]
4 __author__ = 'Kurt Schwehr'
5 __doc__="""
6 Privide a class to allow laying ship tracks down into a grid.
7
8 @requires: U{epydoc<http://epydoc.sourceforge.net/>} > 3.0alpha3
9 @requires: U{numpy<http://numpy.scipy.org/>}
10
11 @author: """+__author__+"""
12 @version: """ + __version__ +"""
13 @var __date__: Date of last svn commit
14 @undocumented: __version__ __author__ __doc__ parser
15 @status: under development
16 @license: GPL v2
17 @since: 2007-Jul-29
18 @todo: allow for non-square grid cells
19 @see: U{Tentative numpy tutorial<http://www.scipy.org/Tentative_NumPy_Tutorial>}
20 """
21
22 from math import *
23 import sys
24
25 import numpy
26
28 return sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) )
29
31 return sqrt( (p2[0]-p1[0])*(p2[0]-p1[0]) + (p2[1]-p1[1])*(p2[1]-p1[1]) )
32
36
38 assert a<b
39 if value<a: return False
40 if value>b: return False
41 return True
42
44 '''
45 x,y in within or on the bounding box
46 '''
47 assert x0<=x1
48 assert y0<=y1
49 if x<x0 or x>x1: return False
50 if y<y0 or y>y1: return False
51 return True
52
54 '''
55 convert a well know text line to a list of points where
56 each point is a tuple
57
58
59 SELECT AsText(Transform(track,32619)) FROM tpath WHERE id=33;
60
61 >>> wktLine2list("LINESTRING(376596 4674402,378419 4668775,376569 4668059)")
62 [(376596.0, 4674402.0), (378419.0, 4668775.0), (376569.0, 4668059.0)]
63 '''
64 start=track.find('(')
65 end =track.rfind(')')
66 pointStrings = track[start+1:end].split(',')
67 list = [ (float(pt.split()[0]),float(pt.split()[1])) for pt in pointStrings ]
68 return list
69
70
72 '''
73 Convert a multisegment line data structure to gnuplot usable line
74 '''
75 o = outfile
76 if name is not None: o.write('# '+name+'\n')
77 for seg in multisegLine:
78 for axis in seg:
79 o.write(str(seg[0])+' '+str(seg[1])+' 0.\n')
80 o.write('\n')
81
82
84 '''
85 lens [((0, 0), 0.0, 0.24874371859296396, 0.99001249960545123), ((0, 1), 0.24874371859296396, 0.5, 1.0000126258640831), ((1, 2), 0.5, 0.75125628140703049, 1.0000126258640547), ((1, 3), 0.75125628140703049, 1.0, 0.99001249960547977)]
86 @param field: 1..3 where 3 is the distance, 1 and 2 are the fractions
87 '''
88
89 o = outfile
90 if name is not None: o.write('# '+name+'\n')
91 for seg in multisegLine:
92 o.write(str(seg[0][0])+' '+str(seg[0][1])+' '+str(seg[field])+'\n\n')
93 o.write('\n')
94
95
96
97
98 gridTypes = [
99 'occurrence'
100 ,'distance'
101 ,'distanceWeightedSpeed'
102 ]
103
105 '''
106 Provide a grid data structure. Will resize up to include the full
107 range on the max size if not an interger size of stepSize. Units are
108 whatever you desire. It is up to you to project them.
109
110 0,0 is at the lower left and (xNumCells-1,yNumCells-1) is the upper right cell
111 '''
112 epsilon = .000001
113 - def __init__(self,minx,miny,maxx,maxy,stepSize,gridType='occurrence',verbose=False):
114 ''' Prepare a grid.
115 Readjust the grid such that the stepSize divides evenly into the ranges.
116 Compute and cache the number of cells.
117 '''
118 self.minx=minx
119 self.miny=miny
120 self.maxx=maxx
121 self.maxy=maxy
122 self.verbose = verbose
123 stepSize=float(stepSize)
124
125 assert gridType in gridTypes
126 self.gridType = gridType
127
128 if verbose:
129 print ' == IN Params =='
130 print ' x:',minx,maxx
131 print ' y:',miny,maxy
132 print ' step:',stepSize
133
134 assert(minx<maxx)
135 assert(miny<maxy)
136 assert(stepSize>0)
137 self.stepSize = stepSize
138 if (maxx-minx)/stepSize > floor((maxx-minx)/stepSize)+self.epsilon:
139 self.maxx = minx + stepSize*floor((maxx-minx)/stepSize) + stepSize
140
141
142 if (maxy-miny)/stepSize > floor((maxy-miny)/stepSize)+self.epsilon:
143 self.maxy = miny + stepSize*floor((maxy-miny)/stepSize) + stepSize
144
145
146 if verbose:
147 print ' Range changes:'
148 print ' maxx:',maxx,'->',self.maxx
149 print ' maxy:',maxy,'->',self.maxy
150
151 self.xNumCells = int(ceil((self.maxx-self.minx)/self.stepSize))
152 self.yNumCells = int(ceil((self.maxy-self.miny)/self.stepSize))
153
154 if verbose:
155 print ' step size num cells deltaMeters'
156 print ' x: ',self.stepSize,' ->',self.xNumCells, ' ', self.maxx - self.minx
157 print ' y: ',self.stepSize,' ->',self.yNumCells, ' ', self.maxy - self.miny
158
159
160
161
162 if gridType=='occurrence':
163 self.grid=numpy.zeros((self.xNumCells+1,self.yNumCells+1),dtype=int)
164 else:
165 self.grid=numpy.zeros((self.xNumCells+1,self.yNumCells+1),dtype=float)
166
168 print ' === GRID === '
169 print ' Bounds ... (%.2f,%.2f) -> (%.2f,%.2f)'%(self.minx,self.miny,self.maxx,self.maxy)
170 print ' Width ...',self.xNumCells
171 print ' Height ...',self.yNumCells
172 print ' CellSize ...',self.stepSize
173
174 - def getCell(self,x,y,verbose=False):
175 '@return: the i,j of the cell containing this coordinate'
176 i = int(floor( (x-self.minx)/self.stepSize ))
177 j = int(floor( (y-self.miny)/self.stepSize ))
178 if verbose: print x,y,'->',i,j, (x-self.miny), self.stepSize
179 return i,j
180
182 '''@param i: the horizonal cell count from the left starting at 0
183 @return the x,y of the center of specified cell'''
184 x = self.minx + self.stepSize * i + 0.5*self.stepSize
185 y = self.miny + self.stepSize * j + 0.5*self.stepSize
186 return x,y
187
189 return self.getLineCells(p1[0],p1[1],p2[0],p2[1],verbose)
190
191
194
196 '''
197 Scan convert a single line segment with two vertices.
198
199 Used for when we need to calculated paraeters off the line to
200 add a value to the cell. e.g. distance or speed wieghted by
201 distance.
202
203 A line inside a cell returns the cell, 0., 1., and the distance
204
205 @return: a list of (cell(an i,j), startFrac, endFrac, distance) for a line
206 @todo: make this actually be fast
207 @todo: switch to delta Y for N-S lines
208 '''
209 if verbose: print '\n\n===============\n getLineCells',x0,y0, x1,y1
210
211
212
213
214
215 flippedX = False
216 if x0>x1:
217 if verbose: print 'flipping X'
218 x0,y0,x1,y1=x1,y1,x0,y0
219 flippedX = True
220
221 startCell = self.getCell(x0,y0,verbose)
222 endCell = self.getCell(x1,y1,verbose)
223 if verbose: print 'cellrange',startCell,endCell,x0,y0,'->',x1,y1
224
225
226 if startCell==endCell:
227 dist = distance(x0,y0, x1,y1)
228 return ( (startCell,0.,1.,dist),)
229
230 dx = x1-x0
231 dy = y1-y0
232 m = slope = (dy/float(dx))
233 b = y0 - slope * x0
234
235
236 stepSize = self.stepSize
237 minx=self.minx; maxx=self.maxx
238 miny=self.miny; maxy=self.maxy
239
240 flippedY = False
241 if y0>y1:
242 if verbose: print 'flipping Y'
243 flippedY = True
244 y0,y1=y1,y0
245 y_first_ycrossing = miny + ceil ((y0 - miny)/stepSize) * stepSize
246 y_last_ycrossing = miny + floor((y1 - miny)/stepSize) * stepSize
247 steps = int( ceil((y_last_ycrossing - y_first_ycrossing)/stepSize + 1))
248
249 if verbose:
250 print 'steps y',y_first_ycrossing,y_last_ycrossing ,'->',steps
251
252 x_ycrossings = [ ]
253 for step in range(steps):
254 y = step*stepSize + y_first_ycrossing
255 x = (y-b)/m
256 x_ycrossings.append(x)
257 del y_first_ycrossing
258 del y_last_ycrossing
259
260 if verbose: print 'x_ycrossings',x_ycrossings
261
262 x_first_xcrossing = minx + ceil((x0 - minx)/stepSize) * stepSize
263 if verbose:
264 print ' x_xcross params:', x0,minx, stepSize, ceil((x0 - minx)/stepSize),
265 print '--->', x_first_xcrossing
266 x_last_xcrossing = minx + floor((x1 - minx)/stepSize) * stepSize
267
268 steps = int( ceil((x_last_xcrossing - x_first_xcrossing)/float(stepSize) +1) )
269 if verbose:
270 print 'steps2',x_first_xcrossing,x_last_xcrossing ,'->',steps
271 print 'steps2',x_last_xcrossing - x_first_xcrossing, (x_last_xcrossing - x_first_xcrossing)/stepSize
272 x_xcrossings = [ ]
273 for step in range(steps):
274 x = step*stepSize + x_first_xcrossing
275 if verbose: print ' X:', step,stepSize,x_first_xcrossing
276 x_xcrossings.append(x)
277 if verbose: print 'x_xcrossings',x_xcrossings
278
279 crossings = x_ycrossings+x_xcrossings
280 if flippedX:
281 if verbose: print 'reverse sort crossings'
282 crossings.sort(reverse=True)
283 else:
284 crossings.sort()
285 if verbose: print '\ncrossings',crossings
286
287
288 cnew=[crossings[0],]
289 for i in range(1,len(crossings)):
290
291 if almostEqual(crossings[i],crossings[i-1]): continue
292 cnew.append(crossings[i])
293
294 if verbose:
295 print 'orig crossings:',crossings
296 print ' new crossings:',cnew
297 crossings=cnew
298
299
300 cells=[]
301 if not flippedX:
302 cells = [startCell,]
303
304 fractions = [0.,]
305
306 totXRange = x1 - x0
307 print 'xrange',totXRange , x1,x0
308 for x in crossings:
309 fractions.append( (x-x0) / totXRange )
310 x = x+0.0001
311 y = m * x + b
312 cells.append(self.getCell(x,y))
313 if flippedX:
314 cells.append(startCell)
315
316 fractions.append(1.)
317 print 'fractions',fractions
318 distances = []
319 for i in range(0,len(fractions)-1):
320 x = x0 + totXRange * fractions[i]
321 y = m * x + b
322 p1 = (x,y)
323 x = x0 + totXRange * fractions[i+1]
324 y = m * x + b
325 p2 = (x,y)
326
327 distances.append(distancePt(p1,p2))
328 print 'dists',distances
329
330
331 assert(len(cells)>1)
332
333 minCellX = min(startCell[0],endCell[0])
334 maxCellX = max(startCell[0],endCell[0])
335 minCellY = min(startCell[1],endCell[1])
336 maxCellY = max(startCell[1],endCell[1])
337
338 print 'minmaxX',minCellX,maxCellX
339 print 'minmaxY',minCellY,maxCellY
340
341
342
343
344 if not inBoundingBox(cells[0][0],cells[0][1],minCellX,minCellY,maxCellX,maxCellY):
345 if verbose: print 'chomp front'
346 assert False
347 cells=cells[1:]
348 if not inBoundingBox(cells[-1][0],cells[-1][1],minCellX,minCellY,maxCellX,maxCellY):
349 if verbose: print 'chomp tail'
350 assert False
351 cells=cells[:-1]
352
353
354
355
356
357
358
359
360
361
362 assert(len(cells)>1)
363 if verbose: print cells
364
365 r = []
366 print 'lens',
367 assert len(fractions)==len(cells)+1
368 assert len(cells)==len(distances)
369 for i in range(len(cells)):
370 r.append((cells[i],fractions[i],fractions[i+1],distances[i]))
371 return r
372
373
375 '''
376 Scan convert a single line segment with two vertices.
377 @return: a list of cells for a line
378 @todo: make this actually be fast
379 '''
380 if verbose: print '\n\n===============\n getLineCells',x0,y0, x1,y1
381
382
383
384
385
386 flippedX = False
387 if x0>x1:
388 if verbose: print 'flipping X'
389 x0,y0,x1,y1=x1,y1,x0,y0
390 flippedX = True
391
392 startCell = self.getCell(x0,y0,verbose)
393 endCell = self.getCell(x1,y1,verbose)
394 if verbose: print 'cellrange',startCell,endCell,x0,y0,'->',x1,y1
395
396
397 if startCell[0]==endCell[0]:
398 yFlipped=False
399 if startCell[1]>endCell[1]:
400 startCell,endCell=endCell,startCell
401 yFlipped=True
402 if verbose:
403 print 'CASE vert or single cell', startCell[1],endCell[1]+1
404 i = startCell[0]
405 cells = [ (i,j) for j in range(startCell[1],endCell[1]+1) ]
406 if yFlipped:
407 cells.reverse()
408 return cells
409
410
411 if startCell[1]==endCell[1]:
412 if verbose: print 'CASE horiz'
413 j = startCell[1]
414
415 cells = [ (i,j) for i in range(startCell[0],endCell[0]+1) ]
416 if flippedX:
417
418 if verbose: print 'reversing based on x (horiz line)'
419 cells.reverse()
420
421 return cells
422
423 if verbose: print 'CASE not easy... must calculate'
424
425 dx = x1-x0
426 dy = y1-y0
427 m = slope = (dy/float(dx))
428 b = y0 - slope * x0
429
430
431 stepSize = self.stepSize
432 minx=self.minx; maxx=self.maxx
433 miny=self.miny; maxy=self.maxy
434
435 flippedY = False
436 if y0>y1:
437 if verbose: print 'flipping Y'
438 flippedY = True
439 y0,y1=y1,y0
440 y_first_ycrossing = miny + ceil ((y0 - miny)/stepSize) * stepSize
441 y_last_ycrossing = miny + floor((y1 - miny)/stepSize) * stepSize
442 steps = int( ceil((y_last_ycrossing - y_first_ycrossing)/stepSize + 1))
443
444 if verbose:
445 print 'steps y',y_first_ycrossing,y_last_ycrossing ,'->',steps
446
447 x_ycrossings = [ ]
448 for step in range(steps):
449 y = step*stepSize + y_first_ycrossing
450 x = (y-b)/m
451 x_ycrossings.append(x)
452 del y_first_ycrossing
453 del y_last_ycrossing
454
455 if verbose: print 'x_ycrossings',x_ycrossings
456
457 x_first_xcrossing = minx + ceil((x0 - minx)/stepSize) * stepSize
458 if verbose:
459 print ' x_xcross params:', x0,minx, stepSize, ceil((x0 - minx)/stepSize),
460 print '--->', x_first_xcrossing
461 x_last_xcrossing = minx + floor((x1 - minx)/stepSize) * stepSize
462
463 steps = int( ceil((x_last_xcrossing - x_first_xcrossing)/float(stepSize) +1) )
464 if verbose:
465 print 'steps2',x_first_xcrossing,x_last_xcrossing ,'->',steps
466 print 'steps2',x_last_xcrossing - x_first_xcrossing, (x_last_xcrossing - x_first_xcrossing)/stepSize
467 x_xcrossings = [ ]
468 for step in range(steps):
469 x = step*stepSize + x_first_xcrossing
470 if verbose: print ' X:', step,stepSize,x_first_xcrossing
471 x_xcrossings.append(x)
472 if verbose: print 'x_xcrossings',x_xcrossings
473
474 crossings = x_ycrossings+x_xcrossings
475 if flippedX:
476 if verbose: print 'reverse sort crossings'
477 crossings.sort(reverse=True)
478 else:
479 crossings.sort()
480 if verbose: print '\ncrossings',crossings
481
482
483 cnew=[crossings[0],]
484 for i in range(1,len(crossings)):
485
486 if almostEqual(crossings[i],crossings[i-1]): continue
487 cnew.append(crossings[i])
488
489 if verbose:
490 print 'orig crossings:',crossings
491 print ' new crossings:',cnew
492 crossings=cnew
493
494
495
496 cells=[]
497 if not flippedX:
498 cells = [startCell,]
499 for x in crossings:
500 x = x+0.0001
501 y = m * x + b
502 cells.append(self.getCell(x,y))
503 if flippedX:
504 cells.append(startCell)
505
506 assert(len(cells)>1)
507
508 minCellX = min(startCell[0],endCell[0])
509 maxCellX = max(startCell[0],endCell[0])
510 minCellY = min(startCell[1],endCell[1])
511 maxCellY = max(startCell[1],endCell[1])
512
513 c = cells[0]
514 if not inRange(c[0],minCellX,maxCellX) or not inRange(c[1],minCellY,maxCellY):
515 if verbose: print 'chomp front'
516 cells=cells[1:]
517 c = cells[-1]
518 if not inRange(c[0],minCellX,maxCellX) or not inRange(c[1],minCellY,maxCellY):
519 if verbose: print 'chomp tail'
520 cells=cells[:-1]
521
522 assert(len(cells)>1)
523 if verbose: print cells
524 return cells
525
527 '''
528 Return the cells for a multi vertex line. Handles the doubling that will happen at each endpoint
529 @param multiSegLine: ((x1,y1),(x2,y2),(x3,y3),(x4,y4)...)
530 '''
531 assert len(multiSegLine)>1
532
533 cells = self.getLineCells2pt(multiSegLine[0],multiSegLine[1])
534 if len(multiSegLine)==2:
535 return cells
536 for i in range(1,len(multiSegLine)-1):
537
538 newCells = self.getLineCells2pt(multiSegLine[i],multiSegLine[i+1])
539 if len(newCells)<1:
540 print 'ACK... must have at least one cell!!! ERROR fail death'
541 print ' ',i,multiSegLine[i],multiSegLine[i+1]
542 print ' ',newCells
543 assert False
544 if newCells[0] == cells[-1]:
545 cells+=newCells[1:]
546 else:
547 cells+=newCells[:-1]
548 return cells
549
550
552 '''
553 Return the cells for a multi vertex line. Returns distances
554 within cells, so that doubling is not an issue
555
556 @param multiSegLine: ((x1,y1),(x2,y2),(x3,y3),(x4,y4)...)
557 @return: list of (cell, startFrac, endFrac, distance)
558 '''
559 assert len(multiSegLine)>1
560
561
562 cells=[]
563 for i in range(len(multiSegLine)-1):
564 newCells = self.getLineCellsWithCrossingsPts(multiSegLine[i],multiSegLine[i+1])
565 cells+=newCells
566 return cells
567
568
570 'Write out the grid lines as gnuplot dat file'
571 o = file(filename,'w')
572
573 minxStr = str(self.minx)+' '
574 maxxStr = str(self.maxx)+' '
575 for yCell in range(self.yNumCells+1):
576 yStr = str(self.miny + yCell*self.stepSize)+' 0\n'
577 o.write(minxStr+yStr)
578 o.write(maxxStr+yStr+'\n')
579
580 minyStr = ' '+str(self.miny)+' 0\n'
581 maxyStr = ' '+str(self.maxy)+' 0\n'
582 for xCell in range(self.xNumCells+1):
583 xStr = str(self.minx + xCell*self.stepSize)
584 o.write(xStr+minyStr)
585 o.write(xStr+maxyStr+'\n')
586
587
589 grid=self.grid
590
591 if verbose: sys.stderr.write('addMultiSegLine cell inserts: (using type '+self.gridType+')\n')
592
593 if 'occurrence' == self.gridType:
594 cells = self.getMultiSegLineCells(multiSegLine)
595 for cell in cells:
596 try:
597 grid[cell[0],cell[1]]+=1
598 if verbose:
599 print ' ',cell[0],cell[1],grid[cell[0],cell[1]]
600 except:
601 print multiSegLine
602 print 'CRAP... grid failure'
603 print ' ',cell, self.xNumCells, self.yNumCells
604 assert False
605 elif 'distance' == self.gridType:
606 result = self.getMultiSegLineCellsWithCrossings(multiSegLine)
607 if verbose: sys.stderr.write(str(result)+'\n')
608 for seg in result:
609
610 cell = seg[0]
611 dist = seg[3]
612 print 'adding:', cell, dist
613 grid[cell[0],cell[1]] += dist
614 print grid
615 elif 'distanceWeightedSpeed' == self.gridType:
616 assert False
617 else:
618 assert False
619
621 '''
622 @param useSquares: if true then write out the height of each cell as a square. False then it writes a point
623 '''
624 assert useSquares==False
625 grid = self.grid
626 o = file(filename,'w')
627 for i in range(grid.shape[0]):
628 for j in range(grid.shape[1]):
629 x,y = self.getCellCenter(i,j)
630 o.write('%f %f %f\n' % (x,y,grid[i,j]))
631
632
634 g = self.grid
635 o = file(filename,'w')
636 o.write('ncols '+str(self.xNumCells)+'\n')
637 o.write('nrows '+str(self.yNumCells)+'\n')
638 o.write('xllcorner '+str(self.minx)+'\n')
639 o.write('yllcorner '+str(self.miny)+'\n')
640 o.write('cellsize '+str(self.stepSize)+'\n')
641 for j in range(self.yNumCells-1,-1,-1):
642
643 zPoints=[]
644 for i in range(self.xNumCells):
645 zPoints.append('%3d' % (g[i,j]))
646 o.write(' '.join(zPoints))
647 o.write('\n')
648
649
650 if __name__=='__main__':
651 from optparse import OptionParser
652 parser = OptionParser(usage="%prog [options]",version="%prog "+__version__)
653 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
654 help='run the documentation tests')
655 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
656 help='run the unit tests')
657 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
658 help='Make the test output verbose')
659
660 (options,args) = parser.parse_args()
661
662 success=True
663 if options.doctest:
664 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
665 sys.argv= [sys.argv[0]]
666 if options.verbose: sys.argv.append('-v')
667 import doctest
668 numfail,numtests=doctest.testmod()
669 if numfail==0: print 'ok'
670 else:
671 print 'FAILED'
672 success=False
673 if not success: sys.exit('Something Failed')
674 del success
675
676 if options.unittest:
677 sys.argv = [sys.argv[0]]
678 if options.verbose: sys.argv.append('-v')
679 import unittest
680 from grid_tests import *
681 unittest.main()
682