123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- #!/usr/bin/env python3
- ############################################################################
- #
- # MODULE: r.tileset
- #
- # AUTHOR(S): Cedric Shock
- # Updated for GRASS7 by Martin Landa, 2009
- #
- # PURPOSE: To produce tilings of regions in other projections.
- #
- # COPYRIGHT: (C) 2006-2009 by Cedric Shoc, Martin Landa, and 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.
- #
- #############################################################################
- # Bugs:
- # Does not know about meridians in projections. However, unlike the usual
- # hack used to find meridians, this code is perfectly happy with arbitrary
- # rotations and flips
- # The following are planned to be fixed in a future version, if it turns out
- # to be necessary for someone:
- # Does not generate optimal tilings. This means that between an appropriate
- # projection for a region and latitude longitude projection, in the
- # degenerate case, it may create tiles demanding up to twice the necessary
- # information. Requesting data from cylindrical projections near their poles
- # results in divergence. You really don't want to use source data from
- # someone who put it in a cylindrical projection near a pole, do you?
- # Not generating "optimal" tilings has another side effect; the sanity
- # of the destination region will not carry over to generating tiles of
- # realistic aspect ratio. This might be a problem for some WMS servers
- # presenting data in a highly inappropriate projection. Do you really
- # want their data?
- # %module
- # % description: Produces tilings of the source projection for use in the destination region and projection.
- # % keyword: raster
- # % keyword: tiling
- # %end
- # %flag
- # % key: g
- # % description: Produces shell script output
- # %end
- # %flag
- # % key: w
- # % description: Produces web map server query string output
- # %end
- # %option
- # % key: region
- # % type: string
- # % description: Name of region to use instead of current region for bounds and resolution
- # %end
- # %option
- # % key: sourceproj
- # % type: string
- # % description: Source projection
- # % required : yes
- # %end
- # %option
- # % key: sourcescale
- # % type: string
- # % description: Conversion factor from units to meters in source projection
- # % answer : 1
- # %end
- # %option
- # % key: destproj
- # % type: string
- # % description: Destination projection, defaults to this location's projection
- # % required : no
- # %end
- # %option
- # % key: destscale
- # % type: string
- # % description: Conversion factor from units to meters in source projection
- # % required : no
- # %end
- # %option
- # % key: maxcols
- # % type: integer
- # % description: Maximum number of columns for a tile in the source projection
- # % answer: 1024
- # %end
- # %option
- # % key: maxrows
- # % type: integer
- # % description: Maximum number of rows for a tile in the source projection
- # % answer: 1024
- # %end
- # %option
- # % key: overlap
- # % type: integer
- # % description: Number of cells tiles should overlap in each direction
- # % answer: 0
- # %end
- # %option G_OPT_F_SEP
- # % description: Output field separator
- # %end
- # Data structures used in this program:
- # A bounding box:
- # 0 -> left, 1-> bottom, 2->right, 3-> top
- # A border:
- # An array of points indexed by 0 for "x" and 4 for "y" + by number 0, 1, 2, and 3
- # A reprojector [0] is name of source projection, [1] is name of destination
- # A projection - [0] is proj.4 text, [1] is scale
- from __future__ import print_function
- import sys
- import tempfile
- import math
- from grass.script.utils import separator
- from grass.script import core as gcore
- from grass.script.utils import decode
- from grass.exceptions import CalledModuleError
- def bboxToPoints(bbox):
- """Make points that are the corners of a bounding box"""
- points = []
- points.append((bbox["w"], bbox["s"]))
- points.append((bbox["w"], bbox["n"]))
- points.append((bbox["e"], bbox["n"]))
- points.append((bbox["e"], bbox["s"]))
- return points
- def pointsToBbox(points):
- bbox = {}
- min_x = min_y = max_x = max_y = None
- for point in points:
- if not min_x:
- min_x = max_x = point[0]
- if not min_y:
- min_y = max_y = point[1]
- if min_x > point[0]:
- min_x = point[0]
- if max_x < point[0]:
- max_x = point[0]
- if min_y > point[1]:
- min_y = point[1]
- if max_y < point[1]:
- max_y = point[1]
- bbox["n"] = max_y
- bbox["s"] = min_y
- bbox["w"] = min_x
- bbox["e"] = max_x
- return bbox
- def project(file, source, dest):
- """Projects point (x, y) using projector"""
- errors = 0
- points = []
- try:
- ret = gcore.read_command(
- "m.proj",
- quiet=True,
- flags="d",
- proj_in=source["proj"],
- proj_out=dest["proj"],
- sep=";",
- input=file,
- )
- ret = decode(ret)
- except CalledModuleError:
- gcore.fatal(cs2cs + " failed")
- if not ret:
- gcore.fatal(cs2cs + " failed")
- for line in ret.splitlines():
- if "*" in line:
- errors += 1
- else:
- p_x2, p_y2, p_z2 = list(map(float, line.split(";")))
- points.append((p_x2 / dest["scale"], p_y2 / dest["scale"]))
- return points, errors
- def projectPoints(points, source, dest):
- """Projects a list of points"""
- dest_points = []
- input = tempfile.NamedTemporaryFile(mode="wt")
- for point in points:
- input.file.write(
- "%f;%f\n" % (point[0] * source["scale"], point[1] * source["scale"])
- )
- input.file.flush()
- dest_points, errors = project(input.name, source, dest)
- return dest_points, errors
- def sideLengths(points, xmetric, ymetric):
- """Find the length of sides of a set of points from one to the next"""
- ret = []
- for i in range(len(points)):
- x1, y1 = points[i]
- j = i + 1
- if j >= len(points):
- j = 0
- sl_x = (points[j][0] - points[i][0]) * xmetric
- sl_y = (points[j][1] - points[i][1]) * ymetric
- sl_d = math.sqrt(sl_x * sl_x + sl_y * sl_y)
- ret.append(sl_d)
- return {"x": (ret[1], ret[3]), "y": (ret[0], ret[2])}
- def bboxesIntersect(bbox_1, bbox_2):
- """Determine if two bounding boxes intersect"""
- bi_a1 = (bbox_1["w"], bbox_1["s"])
- bi_a2 = (bbox_1["e"], bbox_1["n"])
- bi_b1 = (bbox_2["w"], bbox_2["s"])
- bi_b2 = (bbox_2["e"], bbox_2["n"])
- cin = [False, False]
- for i in (0, 1):
- if (
- (bi_a1[i] <= bi_b1[i] and bi_a2[i] >= bi_b1[i])
- or (bi_a1[i] <= bi_b1[i] and bi_a2[i] >= bi_b2[i])
- or (bi_b1[i] <= bi_a1[i] and bi_b2[i] >= bi_a1[i])
- or (bi_b1[i] <= bi_a1[i] and bi_b2[i] >= bi_a2[i])
- ):
- cin[i] = True
- if cin[0] and cin[1]:
- return True
- return False
- def main():
- # Take into account those extra pixels we'll be a addin'
- max_cols = int(options["maxcols"]) - int(options["overlap"])
- max_rows = int(options["maxrows"]) - int(options["overlap"])
- if max_cols == 0:
- gcore.fatal(
- _(
- "It is not possible to set 'maxcols=%s' and "
- "'overlap=%s'. Please set maxcols>overlap"
- % (options["maxcols"], options["overlap"])
- )
- )
- elif max_rows == 0:
- gcore.fatal(
- _(
- "It is not possible to set 'maxrows=%s' and "
- "'overlap=%s'. Please set maxrows>overlap"
- % (options["maxrows"], options["overlap"])
- )
- )
- # destination projection
- if not options["destproj"]:
- dest_proj = gcore.read_command("g.proj", quiet=True, flags="jf")
- dest_proj = decode(dest_proj).rstrip("\n")
- if not dest_proj:
- gcore.fatal(_("g.proj failed"))
- else:
- dest_proj = options["destproj"]
- gcore.debug("Getting destination projection -> '%s'" % dest_proj)
- # projection scale
- if not options["destscale"]:
- ret = gcore.parse_command("g.proj", quiet=True, flags="j")
- if not ret:
- gcore.fatal(_("g.proj failed"))
- if "+to_meter" in ret:
- dest_scale = ret["+to_meter"].strip()
- else:
- gcore.warning(_("Scale (%s) not found, assuming '1'") % "+to_meter")
- dest_scale = "1"
- else:
- dest_scale = options["destscale"]
- gcore.debug("Getting destination projection scale -> %s" % dest_scale)
- # set up the projections
- srs_source = {"proj": options["sourceproj"], "scale": float(options["sourcescale"])}
- srs_dest = {"proj": dest_proj, "scale": float(dest_scale)}
- if options["region"]:
- gcore.run_command("g.region", quiet=True, region=options["region"])
- dest_bbox = gcore.region()
- gcore.debug("Getting destination region")
- # output field separator
- fs = separator(options["separator"])
- # project the destination region into the source:
- gcore.verbose("Projecting destination region into source...")
- dest_bbox_points = bboxToPoints(dest_bbox)
- dest_bbox_source_points, errors_dest = projectPoints(
- dest_bbox_points, source=srs_dest, dest=srs_source
- )
- if len(dest_bbox_source_points) == 0:
- gcore.fatal(
- _(
- "There are no tiles available. Probably the output "
- "projection system it is not compatible with the "
- "projection of the current location"
- )
- )
- source_bbox = pointsToBbox(dest_bbox_source_points)
- gcore.verbose("Projecting source bounding box into destination...")
- source_bbox_points = bboxToPoints(source_bbox)
- source_bbox_dest_points, errors_source = projectPoints(
- source_bbox_points, source=srs_source, dest=srs_dest
- )
- x_metric = 1 / dest_bbox["ewres"]
- y_metric = 1 / dest_bbox["nsres"]
- gcore.verbose("Computing length of sides of source bounding box...")
- source_bbox_dest_lengths = sideLengths(source_bbox_dest_points, x_metric, y_metric)
- # Find the skewedness of the two directions.
- # Define it to be greater than one
- # In the direction (x or y) in which the world is least skewed (ie north south in lat long)
- # Divide the world into strips. These strips are as big as possible contrained by max_
- # In the other direction do the same thing.
- # There's some recomputation of the size of the world that's got to come in
- # here somewhere.
- # For now, however, we are going to go ahead and request more data than is necessary.
- # For small regions far from the critical areas of projections this makes very little difference
- # in the amount of data gotten.
- # We can make this efficient for big regions or regions near critical
- # points later.
- bigger = []
- bigger.append(max(source_bbox_dest_lengths["x"]))
- bigger.append(max(source_bbox_dest_lengths["y"]))
- maxdim = (max_cols, max_rows)
- # Compute the number and size of tiles to use in each direction
- # I'm making fairly even sized tiles
- # They differer from each other in height and width only by one cell
- # I'm going to make the numbers all simpler and add this extra cell to
- # every tile.
- gcore.message(_("Computing tiling..."))
- tiles = [-1, -1]
- tile_base_size = [-1, -1]
- tiles_extra_1 = [-1, -1]
- tile_size = [-1, -1]
- tileset_size = [-1, -1]
- tile_size_overlap = [-1, -1]
- for i in range(len(bigger)):
- # make these into integers.
- # round up
- bigger[i] = int(bigger[i] + 1)
- tiles[i] = int((bigger[i] / maxdim[i]) + 1)
- tile_size[i] = tile_base_size[i] = int(bigger[i] / tiles[i])
- tiles_extra_1[i] = int(bigger[i] % tiles[i])
- # This is adding the extra pixel (remainder) to all of the tiles:
- if tiles_extra_1[i] > 0:
- tile_size[i] = tile_base_size[i] + 1
- tileset_size[i] = int(tile_size[i] * tiles[i])
- # Add overlap to tiles (doesn't effect tileset_size
- tile_size_overlap[i] = tile_size[i] + int(options["overlap"])
- gcore.verbose(
- "There will be %d by %d tiles each %d by %d cells"
- % (tiles[0], tiles[1], tile_size[0], tile_size[1])
- )
- ximax = tiles[0]
- yimax = tiles[1]
- min_x = source_bbox["w"]
- min_y = source_bbox["s"]
- max_x = source_bbox["e"]
- max_y = source_bbox["n"]
- span_x = max_x - min_x
- span_y = max_y - min_y
- xi = 0
- tile_bbox = {"w": -1, "s": -1, "e": -1, "n": -1}
- if errors_dest > 0:
- gcore.warning(
- _("During computation %i tiles could not be created" % errors_dest)
- )
- while xi < ximax:
- tile_bbox["w"] = float(min_x) + (
- float(xi) * float(tile_size[0]) / float(tileset_size[0])
- ) * float(span_x)
- tile_bbox["e"] = float(min_x) + (
- float(xi + 1) * float(tile_size_overlap[0]) / float(tileset_size[0])
- ) * float(span_x)
- yi = 0
- while yi < yimax:
- tile_bbox["s"] = float(min_y) + (
- float(yi) * float(tile_size[1]) / float(tileset_size[1])
- ) * float(span_y)
- tile_bbox["n"] = float(min_y) + (
- float(yi + 1) * float(tile_size_overlap[1]) / float(tileset_size[1])
- ) * float(span_y)
- tile_bbox_points = bboxToPoints(tile_bbox)
- tile_dest_bbox_points, errors = projectPoints(
- tile_bbox_points, source=srs_source, dest=srs_dest
- )
- tile_dest_bbox = pointsToBbox(tile_dest_bbox_points)
- if bboxesIntersect(tile_dest_bbox, dest_bbox):
- if flags["w"]:
- print(
- "bbox=%s,%s,%s,%s&width=%s&height=%s"
- % (
- tile_bbox["w"],
- tile_bbox["s"],
- tile_bbox["e"],
- tile_bbox["n"],
- tile_size_overlap[0],
- tile_size_overlap[1],
- )
- )
- elif flags["g"]:
- print(
- "w=%s;s=%s;e=%s;n=%s;cols=%s;rows=%s"
- % (
- tile_bbox["w"],
- tile_bbox["s"],
- tile_bbox["e"],
- tile_bbox["n"],
- tile_size_overlap[0],
- tile_size_overlap[1],
- )
- )
- else:
- print(
- "%s%s%s%s%s%s%s%s%s%s%s"
- % (
- tile_bbox["w"],
- fs,
- tile_bbox["s"],
- fs,
- tile_bbox["e"],
- fs,
- tile_bbox["n"],
- fs,
- tile_size_overlap[0],
- fs,
- tile_size_overlap[1],
- )
- )
- yi += 1
- xi += 1
- if __name__ == "__main__":
- cs2cs = "cs2cs"
- options, flags = gcore.parser()
- sys.exit(main())
|