def get_datasource(self, logger): bbox = self._mercator_bbox dst_bbox = bbox.bounds dst_x_size = self.size dst_y_size = self.size dst_srs = osr.SpatialReference() dst_srs.ImportFromEPSG(3857) dst_drv = gdal.GetDriverByName("MEM") dst_ds = dst_drv.Create('', dst_x_size, dst_y_size, 1, gdal.GDT_Float32) dst_x_res = float(dst_bbox[2] - dst_bbox[0]) / dst_x_size dst_y_res = float(dst_bbox[3] - dst_bbox[1]) / dst_y_size dst_gt = (dst_bbox[0], dst_x_res, 0, dst_bbox[3], 0, -dst_y_res) dst_ds.SetGeoTransform(dst_gt) dst_ds.SetProjection(dst_srs.ExportToWkt()) dst_ds.GetRasterBand(1).SetNoDataValue(FLT_NODATA) # figure out what the approximate scale of the output image is in # lat/lon coordinates. this is used to select the appropriate filter. ll_bbox = self.latlon_bbox() ll_x_res = float(ll_bbox.bounds[2] - ll_bbox.bounds[0]) / dst_x_size ll_y_res = float(ll_bbox.bounds[3] - ll_bbox.bounds[1]) / dst_y_size composite.compose(self, dst_ds, logger, min(ll_x_res, ll_y_res)) try: yield dst_ds finally: del dst_ds
def render(self, tmp_dir): logger = logging.getLogger('skadi') bbox = _bbox(self.x, self.y) mid_dir = os.path.join(tmp_dir, self.output_dir, ("N" if self.y >= 90 else "S") + ("%02d" % abs(self.y - 90))) if not os.path.isdir(mid_dir): try: os.makedirs(mid_dir) except OSError as e: # swallow the error if the directory exists - it's # probably another thread creating it. if e.errno != errno.EEXIST or not os.path.isdir(mid_dir): raise tile = _tile_name(self.x, self.y) hgt_file = os.path.join(mid_dir, tile + ".hgt") tile_file = os.path.join(mid_dir, tile + ".hgt.gz") logger.info("Generating tile %r..." % tile) dst_bbox = bbox.bounds dst_x_size = 3601 dst_y_size = 3601 dst_srs = osr.SpatialReference() dst_srs.ImportFromEPSG(4326) # for SRTM, must first buffer in memory, then write to disk with # CreateCopy. dst_drv = gdal.GetDriverByName("MEM") dst_ds = dst_drv.Create('', dst_x_size, dst_y_size, 1, gdal.GDT_Int16) dst_x_res = float(dst_bbox[2] - dst_bbox[0]) / dst_x_size dst_y_res = float(dst_bbox[3] - dst_bbox[1]) / dst_y_size dst_gt = (dst_bbox[0], dst_x_res, 0, dst_bbox[3], 0, -dst_y_res) dst_ds.SetGeoTransform(dst_gt) dst_ds.SetProjection(dst_srs.ExportToWkt()) dst_ds.GetRasterBand(1).SetNoDataValue(-32768) composite.compose(self, dst_ds, logger, min(dst_x_res, dst_y_res)) logger.debug("Writing SRTMHGT: %r" % hgt_file) srtm_drv = gdal.GetDriverByName("SRTMHGT") srtm_ds = srtm_drv.CreateCopy(hgt_file, dst_ds) del dst_ds del srtm_ds logger.debug("Compressing HGT -> GZ: %r" % tile_file) with gzip.open(tile_file, 'wb') as gz, open(hgt_file, 'rb') as hgt: shutil.copyfileobj(hgt, gz) os.remove(hgt_file) assert os.path.isfile(tile_file) logger.info("Done generating tile %r" % tile)
def render(self, tmp_dir): logger = logging.getLogger('normal') bbox = self._mercator_bbox mid_dir = os.path.join(tmp_dir, self.output_dir, str(self.z), str(self.x)) mkdir_p(mid_dir) tile = self.tile_name() tile_file = os.path.join(tmp_dir, self.output_dir, tile + ".png") logger.debug("Generating tile %r..." % tile) filter_size = 10 outfile = tile_file dst_bbox = bbox.bounds dst_x_size = 256 dst_y_size = 256 dst_x_res = float(dst_bbox[2] - dst_bbox[0]) / dst_x_size dst_y_res = float(dst_bbox[3] - dst_bbox[1]) / dst_y_size dst_srs = osr.SpatialReference() dst_srs.ImportFromEPSG(3857) # expand bbox & image to generate "bleed" for image filter mid_min_x = dst_bbox[0] - filter_size * dst_x_res mid_min_y = dst_bbox[1] - filter_size * dst_y_res mid_max_x = dst_bbox[2] + filter_size * dst_x_res mid_max_y = dst_bbox[3] + filter_size * dst_y_res filter_top_margin = filter_size filter_bot_margin = filter_size filter_lft_margin = filter_size filter_rgt_margin = filter_size # clip bounding box back to the edges of the world. GDAL can handle # wrapping around the world, but it doesn't give the results that # would be expected. if mid_min_x < -0.5 * mercator.MERCATOR_WORLD_SIZE: filter_lft_margin = 0 mid_min_x = dst_bbox[0] if mid_min_y < -0.5 * mercator.MERCATOR_WORLD_SIZE: filter_bot_margin = 0 mid_min_y = dst_bbox[1] if mid_max_x > 0.5 * mercator.MERCATOR_WORLD_SIZE: filter_rgt_margin = 0 mid_max_x = dst_bbox[2] if mid_max_y > 0.5 * mercator.MERCATOR_WORLD_SIZE: filter_top_margin = 0 mid_max_y = dst_bbox[3] mid_x_size = dst_x_size + filter_lft_margin + filter_rgt_margin mid_y_size = dst_y_size + filter_bot_margin + filter_top_margin mid_bbox = (mid_min_x, mid_min_y, mid_max_x, mid_max_y) mid_drv = gdal.GetDriverByName("MEM") mid_ds = mid_drv.Create('', mid_x_size, mid_y_size, 1, gdal.GDT_Float32) mid_gt = (mid_bbox[0], dst_x_res, 0, mid_bbox[3], 0, -dst_y_res) mid_ds.SetGeoTransform(mid_gt) mid_ds.SetProjection(dst_srs.ExportToWkt()) mid_ds.GetRasterBand(1).SetNoDataValue(mercator.FLT_NODATA) # figure out what the approximate scale of the output image is in # lat/lon coordinates. this is used to select the appropriate filter. ll_bbox = self._latlon_bbox ll_x_res = float(ll_bbox.bounds[2] - ll_bbox.bounds[0]) / dst_x_size ll_y_res = float(ll_bbox.bounds[3] - ll_bbox.bounds[1]) / dst_y_size # calculate the resolution of a pixel in real meters for both x and y. # this will be used to scale the gradient so that it's consistent # across zoom levels. ll_mid_x = 0.5 * (ll_bbox.bounds[2] + ll_bbox.bounds[0]) ll_spc_x = 0.5 * (ll_bbox.bounds[2] - ll_bbox.bounds[0]) / dst_x_size ll_mid_y = 0.5 * (ll_bbox.bounds[3] + ll_bbox.bounds[1]) ll_spc_y = 0.5 * (ll_bbox.bounds[3] - ll_bbox.bounds[1]) / dst_y_size geod = Geodesic.WGS84 # NOTE: in defiance of predictability and regularity, the geod methods # take input as (lat, lon) in that order, rather than (x, y) as would # be sensible. # NOTE: at low zooms, taking the width across the tile starts to break # down, so we take the width across a small portion of the interior of # the tile instead. geodesic_res_x = -1.0 / \ geod.Inverse(ll_mid_y, ll_mid_x - ll_spc_x, ll_mid_y, ll_mid_x + ll_spc_x)['s12'] geodesic_res_y = 1.0 / \ geod.Inverse(ll_mid_y - ll_spc_y, ll_mid_x, ll_mid_y + ll_spc_y, ll_mid_x)['s12'] composite.compose(self, mid_ds, logger, min(ll_x_res, ll_y_res)) pixels = mid_ds.GetRasterBand(1).ReadAsArray(0, 0, mid_x_size, mid_y_size) ygrad, xgrad = numpy.gradient(pixels, 2) img = numpy.dstack((geodesic_res_x * xgrad, geodesic_res_y * ygrad, numpy.ones((mid_y_size, mid_x_size)))) # first, we normalise to unit vectors. this puts each element of img # in the range (-1, 1). the "einsum" stuff is serious black magic, but # what it (should be) saying is "for each i,j in the rows and columns, # the output is the sum of img[i,j,k]*img[i,j,k]" - i.e: the square. norm = numpy.sqrt(numpy.einsum('ijk,ijk->ij', img, img)) # the norm is now the "wrong shape" according to numpy, so we need to # copy the norm value out into RGB components. norm_copy = norm[:, :, numpy.newaxis] # dividing the img by norm_copy should give us RGB components with # values between -1 and 1, but we need values between 0 and 255 for # PNG channels. so we move and scale the values to fit in that range. scaled = (128.0 * (img / norm_copy + 1.0)) # and finally clip it to (0, 255) just in case img = numpy.clip(scaled, 0.0, 255.0) # Create output as a 4-channel RGBA image, each (byte) channel # corresponds to x, y, z, h where x, y and z are the respective # components of the normal, and h is an index into a hypsometric tint # table (see HEIGHT_TABLE). dst_ds = mid_drv.Create('', dst_x_size, dst_y_size, 4, gdal.GDT_Byte) dst_gt = (dst_bbox[0], dst_x_res, 0, dst_bbox[3], 0, -dst_y_res) dst_ds.SetGeoTransform(dst_gt) dst_ds.SetProjection(dst_srs.ExportToWkt()) # apply the height mapping function to get the table index. func = numpy.vectorize(_height_mapping_func) hyps = func(pixels).astype(numpy.uint8) # extract the area without the "bleed" margin. ext = img[filter_top_margin:(filter_top_margin+dst_y_size), \ filter_lft_margin:(filter_lft_margin+dst_x_size)] dst_ds.GetRasterBand(1).WriteArray(ext[...,0].astype(numpy.uint8)) dst_ds.GetRasterBand(2).WriteArray(ext[...,1].astype(numpy.uint8)) dst_ds.GetRasterBand(3).WriteArray(ext[...,2].astype(numpy.uint8)) # add hypsometric tint index as alpha channel dst_ds.GetRasterBand(4).WriteArray( hyps[filter_top_margin:(filter_top_margin+dst_y_size), filter_lft_margin:(filter_lft_margin+dst_x_size)]) png_drv = gdal.GetDriverByName("PNG") png_ds = png_drv.CreateCopy(tile_file, dst_ds) # explicitly delete the datasources. the Python-GDAL docs suggest that # this is a good idea not only to dispose of memory buffers but also # to ensure that the backing file handles are closed. del png_ds del dst_ds del mid_ds assert os.path.isfile(tile_file) source_names = [type(s).__name__ for s in self.sources] logger.info("Done generating tile %r from %s" % (tile, ", ".join(source_names)))