3
############################################################################
7
# AUTHOR(S): Cedric Shock
8
# Updated for GRASS7 by Martin Landa, 2009
10
# PURPOSE: To produce tilings of regions in other projections.
12
# COPYRIGHT: (C) 2006-2009 by Cedric Shoc, Martin Landa, and GRASS development team
14
# This program is free software under the GNU General
15
# Public License (>=v2). Read the file COPYING that
16
# comes with GRASS for details.
18
#############################################################################
21
# Does not know about meridians in projections. However, unlike the usual
22
# hack used to find meridians, this code is perfectly happy with arbitrary
24
# The following are planned to be fixed in a future version, if it turns out
25
# to be necessary for someone:
26
# Does not generate optimal tilings. This means that between an appropriate
27
# projection for a region and latitude longitude projection, in the
28
# degenerate case, it may create tiles demanding up to twice the necessary
29
# information. Requesting data from cylindrical projections near their poles
30
# results in divergence. You really don't want to use source data from
31
# someone who put it in a cylindrical projection near a pole, do you?
32
# Not generating "optimal" tilings has another side effect; the sanity
33
# of the destination region will not carry over to generating tiles of
34
# realistic aspect ratio. This might be a problem for some WMS servers
35
# presenting data in a highly inappropriate projection. Do you really
39
#% description: Produces tilings of the source projection for use in the destination region and projection.
45
#% description: Produces shell script output
49
#% description: Produces web map server query string output
54
#% description: Name of region to use instead of current region for bounds and resolution
59
#% description: Source projection
65
#% description: Conversion factor from units to meters in source projection
71
#% description: Destination projection, defaults to this location's projection
77
#% description: Conversion factor from units to meters in source projection
83
#% description: Maximum number of columns for a tile in the source projection
89
#% description: Maximum number of rows for a tile in the source projection
95
#% description: Number of cells tiles should overlap in each direction
99
#% description: Output field separator
102
# Data structures used in this program:
104
# 0 -> left, 1-> bottom, 2->right, 3-> top
106
# An array of points indexed by 0 for "x" and 4 for "y" + by number 0, 1, 2, and 3
107
# A reprojector [0] is name of source projection, [1] is name of destination
108
# A projection - [0] is proj.4 text, [1] is scale
114
from grass.script.utils import separator
115
from grass.script import core as grass
116
from grass.exceptions import CalledModuleError
119
def bboxToPoints(bbox):
120
"""Make points that are the corners of a bounding box"""
122
points.append((bbox['w'], bbox['s']))
123
points.append((bbox['w'], bbox['n']))
124
points.append((bbox['e'], bbox['n']))
125
points.append((bbox['e'], bbox['s']))
130
def pointsToBbox(points):
132
min_x = min_y = max_x = max_y = None
135
min_x = max_x = point[0]
137
min_y = max_y = point[1]
156
def project(file, source, dest):
157
"""Projects point (x, y) using projector"""
161
ret = grass.read_command('m.proj',
164
proj_in=source['proj'],
165
proj_out=dest['proj'],
168
except CalledModuleError:
169
grass.fatal(cs2cs + ' failed')
172
grass.fatal(cs2cs + ' failed')
174
for line in ret.splitlines():
178
p_x2, p_y2, p_z2 = map(float, line.split(';'))
179
points.append((p_x2 / dest['scale'], p_y2 / dest['scale']))
181
return points, errors
184
def projectPoints(points, source, dest):
185
"""Projects a list of points"""
188
input = tempfile.NamedTemporaryFile(mode="wt")
190
input.file.write('%f;%f\n' % (point[0] * source['scale'],
191
point[1] * source['scale']))
194
dest_points, errors = project(input.name, source, dest)
196
return dest_points, errors
199
def sideLengths(points, xmetric, ymetric):
200
"""Find the length of sides of a set of points from one to the next"""
202
for i in range(len(points)):
207
sl_x = (points[j][0] - points[i][0]) * xmetric
208
sl_y = (points[j][1] - points[i][1]) * ymetric
209
sl_d = math.sqrt(sl_x * sl_x + sl_y * sl_y)
212
return {'x': (ret[1], ret[3]), 'y': (ret[0], ret[2])}
215
def bboxesIntersect(bbox_1, bbox_2):
216
"""Determine if two bounding boxes intersect"""
217
bi_a1 = (bbox_1['w'], bbox_1['s'])
218
bi_a2 = (bbox_1['e'], bbox_1['n'])
219
bi_b1 = (bbox_2['w'], bbox_2['s'])
220
bi_b2 = (bbox_2['e'], bbox_2['n'])
223
if (bi_a1[i] <= bi_b1[i] and bi_a2[i] >= bi_b1[i]) or \
224
(bi_a1[i] <= bi_b1[i] and bi_a2[i] >= bi_b2[i]) or \
225
(bi_b1[i] <= bi_a1[i] and bi_b2[i] >= bi_a1[i]) or \
226
(bi_b1[i] <= bi_a1[i] and bi_b2[i] >= bi_a2[i]):
229
if cin[0] and cin[1]:
236
# Take into account those extra pixels we'll be a addin'
237
max_cols = int(options['maxcols']) - int(options['overlap'])
238
max_rows = int(options['maxrows']) - int(options['overlap'])
241
grass.fatal(_("It is not possibile to set 'maxcols=%s' and "
242
"'overlap=%s'. Please set maxcols>overlap" %
243
(options['maxcols'], options['overlap'])))
245
grass.fatal(_("It is not possibile to set 'maxrows=%s' and "
246
"'overlap=%s'. Please set maxrows>overlap" %
247
(options['maxrows'], options['overlap'])))
248
# destination projection
249
if not options['destproj']:
250
dest_proj = grass.read_command('g.proj',
252
flags='jf').rstrip('\n')
254
grass.fatal(_('g.proj failed'))
256
dest_proj = options['destproj']
257
grass.debug("Getting destination projection -> '%s'" % dest_proj)
260
if not options['destscale']:
261
ret = grass.parse_command('g.proj',
265
grass.fatal(_('g.proj failed'))
267
if '+to_meter' in ret:
268
dest_scale = ret['+to_meter'].strip()
270
grass.warning(_("Scale (%s) not found, assuming '1'") % '+to_meter')
273
dest_scale = options['destscale']
274
grass.debug('Getting destination projection scale -> %s' % dest_scale)
276
# set up the projections
277
srs_source = {'proj': options['sourceproj'],
278
'scale': float(options['sourcescale'])}
279
srs_dest = {'proj': dest_proj, 'scale': float(dest_scale)}
281
if options['region']:
282
grass.run_command('g.region',
284
region=options['region'])
285
dest_bbox = grass.region()
286
grass.debug('Getting destination region')
288
# output field separator
289
fs = separator(options['separator'])
291
# project the destination region into the source:
292
grass.verbose('Projecting destination region into source...')
293
dest_bbox_points = bboxToPoints(dest_bbox)
295
dest_bbox_source_points, errors_dest = projectPoints(dest_bbox_points,
299
if len(dest_bbox_source_points) == 0:
300
grass.fatal(_("There are no tiles available. Probably the output "
301
"projection system it is not compatible with the "
302
"projection of the current location"))
304
source_bbox = pointsToBbox(dest_bbox_source_points)
306
grass.verbose('Projecting source bounding box into destination...')
308
source_bbox_points = bboxToPoints(source_bbox)
310
source_bbox_dest_points, errors_source = projectPoints(source_bbox_points,
314
x_metric = 1 / dest_bbox['ewres']
315
y_metric = 1 / dest_bbox['nsres']
317
grass.verbose('Computing length of sides of source bounding box...')
319
source_bbox_dest_lengths = sideLengths(source_bbox_dest_points,
322
# Find the skewedness of the two directions.
323
# Define it to be greater than one
324
# In the direction (x or y) in which the world is least skewed (ie north south in lat long)
325
# Divide the world into strips. These strips are as big as possible contrained by max_
326
# In the other direction do the same thing.
327
# Theres some recomputation of the size of the world that's got to come in here somewhere.
329
# For now, however, we are going to go ahead and request more data than is necessary.
330
# For small regions far from the critical areas of projections this makes very little difference
331
# in the amount of data gotten.
332
# We can make this efficient for big regions or regions near critical points later.
335
bigger.append(max(source_bbox_dest_lengths['x']))
336
bigger.append(max(source_bbox_dest_lengths['y']))
337
maxdim = (max_cols, max_rows)
339
# Compute the number and size of tiles to use in each direction
340
# I'm making fairly even sized tiles
341
# They differer from each other in height and width only by one cell
342
# I'm going to make the numbers all simpler and add this extra cell to
345
grass.message(_('Computing tiling...'))
347
tile_base_size = [-1, -1]
348
tiles_extra_1 = [-1, -1]
350
tileset_size = [-1, -1]
351
tile_size_overlap = [-1, -1]
352
for i in range(len(bigger)):
353
# make these into integers.
355
bigger[i] = int(bigger[i] + 1)
356
tiles[i] = int((bigger[i] / maxdim[i]) + 1)
357
tile_size[i] = tile_base_size[i] = int(bigger[i] / tiles[i])
358
tiles_extra_1[i] = int(bigger[i] % tiles[i])
359
# This is adding the extra pixel (remainder) to all of the tiles:
360
if tiles_extra_1[i] > 0:
361
tile_size[i] = tile_base_size[i] + 1
362
tileset_size[i] = int(tile_size[i] * tiles[i])
363
# Add overlap to tiles (doesn't effect tileset_size
364
tile_size_overlap[i] = tile_size[i] + int(options['overlap'])
366
grass.verbose("There will be %d by %d tiles each %d by %d cells" %
367
(tiles[0], tiles[1], tile_size[0], tile_size[1]))
372
min_x = source_bbox['w']
373
min_y = source_bbox['s']
374
max_x = source_bbox['e']
375
max_y = source_bbox['n']
376
span_x = (max_x - min_x)
377
span_y = (max_y - min_y)
380
tile_bbox = {'w': -1, 's': -1, 'e': -1, 'n': -1}
383
grass.warning(_("During computation %i tiles could not be created" %
387
tile_bbox['w'] = float(min_x) + (float(xi) * float(tile_size[0]) / float(tileset_size[0])) * float(span_x)
388
tile_bbox['e'] = float(min_x) + (float(xi + 1) * float(tile_size_overlap[0]) / float(tileset_size[0])) * float(span_x)
391
tile_bbox['s'] = float(min_y) + (float(yi) * float(tile_size[1]) / float(tileset_size[1])) * float(span_y)
392
tile_bbox['n'] = float(min_y) + (float(yi + 1) * float(tile_size_overlap[1]) / float(tileset_size[1])) * float(span_y)
393
tile_bbox_points = bboxToPoints(tile_bbox)
394
tile_dest_bbox_points, errors = projectPoints(tile_bbox_points,
397
tile_dest_bbox = pointsToBbox(tile_dest_bbox_points)
398
if bboxesIntersect(tile_dest_bbox, dest_bbox):
400
print "bbox=%s,%s,%s,%s&width=%s&height=%s" % \
401
(tile_bbox['w'], tile_bbox['s'], tile_bbox['e'],
402
tile_bbox['n'], tile_size_overlap[0],
403
tile_size_overlap[1])
405
print "w=%s;s=%s;e=%s;n=%s;cols=%s;rows=%s" % \
406
(tile_bbox['w'], tile_bbox['s'], tile_bbox['e'],
407
tile_bbox['n'], tile_size_overlap[0],
408
tile_size_overlap[1])
410
print "%s%s%s%s%s%s%s%s%s%s%s" % \
411
(tile_bbox['w'], fs, tile_bbox['s'], fs,
412
tile_bbox['e'], fs, tile_bbox['n'], fs,
413
tile_size_overlap[0], fs, tile_size_overlap[1])
417
if __name__ == "__main__":
419
options, flags = grass.parser()