获取瓦片左上角坐标的python代码 python瓦片地图_Image

 下载代码 仅供参考.瓦片服务是我们自己搭的服务器,参考geoserver。

import io
import math
import multiprocessing
import time
import urllib.request as ur
from threading import Thread
import traceback
import PIL.Image as pil
from pathlib import Path

from numpy.lib.type_check import imag
import sys
# from PIL import Image
# import Image
import cv2
import numpy as np
from osgeo import gdal, osr
from tools import *
# from tqdm import tqdm, trange


# -----------------------------------------------------------

# ---------------------------------------------------------
class Downloader(Thread):
    # multiple threads downloader
    def __init__(self, index, count, urls, datas):
        # index represents the number of threads
        # count represents the total number of threads
        # urls represents the list of URLs nedd to be downloaded
        # datas represents the list of data need to be returned.
        super().__init__()
        self.urls = urls
        self.datas = datas
        self.index = index
        self.count = count

    def download(self, url):
        print("下载图片地址",url,)
        HEADERS = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68'}
        header = ur.Request(url, headers=HEADERS)
        err = 1
        while err < 11:
            try:
                data = ur.urlopen(header, timeout=10).read()
            except:
                traceback.print_exc()
                print("下载错误 重新下载...")
                err += 1
            else:
                img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)
                print("最小像素点", np.min(img))
                if np.min(img) == 255:
                    print(f"图片是空白的,开始重新下载第{err}次")
                    err += 1
                else:
                    return data
        raise Exception("网络连接异常.")

    def run(self):
        for i, url in enumerate(self.urls):
            print("下载任务", i, "/", len(self.urls))
            tile_x, tile_y, z = url.split("&xyz=")[1].split(",")
            # lon, lat = tile2lonlat(tile_x, tile_y, z)
            # 如果i除线程总数1取余 不等于 第几个线程 总是0
            if i % self.count != self.index:
                print("warning!!!跳过该url,记录一下", url)
                continue
            self.datas[str(tile_x)+"_"+str(tile_y)] = self.download(url)


# ---------------------------------------------------------

# ---------------------------------------------------------
def getExtent(x1, y1, x2, y2, z, source="Google China"):
    pos1x, pos1y = wgs_to_tile(x1, y1, z)
    pos2x, pos2y = wgs_to_tile(x2, y2, z)
    Xframe = pixls_to_mercator(
        {"LT": (pos1x, pos1y), "RT": (pos2x, pos1y), "LB": (pos1x, pos2y), "RB": (pos2x, pos2y), "z": z})
    for i in ["LT", "LB", "RT", "RB"]:
        Xframe[i] = mercator_to_wgs(*Xframe[i])
    if source == "Google":
        pass
    elif source == "ZKLF":
        pass
    elif source == "Google China":
        for i in ["LT", "LB", "RT", "RB"]:
            Xframe[i] = gcj_to_wgs(*Xframe[i])
    else:
        raise Exception("Invalid argument: source.")
    return Xframe


def saveTiff(r, g, b, gt, filePath):
    fname_out = filePath
    driver = gdal.GetDriverByName('GTiff')
    # Create a 3-band dataset
    dset_output = driver.Create(fname_out, r.shape[1], r.shape[0], 3, gdal.GDT_Byte)
    dset_output.SetGeoTransform(gt)
    try:
        proj = osr.SpatialReference()
        proj.ImportFromEPSG(4326)
        dset_output.SetSpatialRef(proj)
    except:
        print("Error: Coordinate system setting failed")
    dset_output.GetRasterBand(1).WriteArray(r)
    dset_output.GetRasterBand(2).WriteArray(g)
    dset_output.GetRasterBand(3).WriteArray(b)
    dset_output.FlushCache()
    dset_output = None
    print("Image Saved tif图片生成成功")


# ---------------------------------------------------------

# ---------------------------------------------------------
MAP_URLS = {
    "onwer_server": "http://ip:port/geoserver/ows?service=WMS&version=1.3.0&transparent=true&request=GetMap&layers={style}&width=256&height=256&srs=EPSG%3A3857&format=image/png&bbox={bbox}&xyz={xyz}",
    "Google": "http://mts0.googleapis.com/vt?lyrs={style}&x={x}&y={y}&z={z}",
    "Google China": "http://mt2.google.cn/vt/lyrs={style}&hl=zh-CN&gl=CN&src=app&x={x}&y={y}&z={z}&s=Galile"}


def tile2lonlat(x, y, z):
    """
    @description  :切图坐标转lonlat
    ---------
    @param x :瓦片的x轴
    @param y :瓦片的y轴
    @param z :瓦片的层级
    -------
    @Returns  :[经度, 纬度]
    -------
    """
    x = float(x)
    y = float(y)
    n = math.pow(2, int(z))
    lon = x / n * 360.0 - 180.0
    lat = math.atan(math.sinh(math.pi * (1 - 2 * y / n)))
    lat = lat * 180.0 / math.pi
    return [lon, lat]


def get_url(source, x, y, z, style):  #
    if source == 'Google China':
        url = MAP_URLS["Google China"].format(x=x, y=y, z=z, style=style)
    elif source == 'Google':
        url = MAP_URLS["Google"].format(x=x, y=y, z=z, style=style)
    elif source == "onwer_server":
        min_xy_list = tile2lonlat(x, y + 1, z)
        max_xy_list = tile2lonlat(x + 1, y, z)
        print("min_xy_list:", min_xy_list)
        print("max_xy_list:", max_xy_list)
        lng_min, lat_min = min_xy_list[0], min_xy_list[1]
        lng_max, lat_max = max_xy_list[0], max_xy_list[1]
        # gcj02转wgs84
        # lng_min, lat_min = GCJ02_to_WGS84(min_xy_list[0], min_xy_list[1])
        lng_min, lat_min = WGS84_to_WebMercator(lng_min, lat_min)
        # lng_max, lat_max = GCJ02_to_WGS84(max_xy_list[0], max_xy_list[1])
        lng_max, lat_max = WGS84_to_WebMercator(lng_max, lat_max)
        bbox = ",".join([str(lng_min), str(lat_min), str(lng_max), str(lat_max)])
        xyz = ",".join([str(x), str(y), str(z)])
        url = MAP_URLS["ZKLF"].format(bbox=bbox, style=style, xyz=xyz)
    else:
        raise Exception("Unknown Map Source ! ")
    return url


def get_urls(x1, y1, x2, y2, z, source, style):
    """
    @description  :左上角x1,y1右下角x2,y2
    ---------
    @param  :
    -------
    @Returns  :
    -------
    """
    pos1x, pos1y = wgs_to_tile(x1, y1, z)  # 左上角的瓦片图坐标
    pos2x, pos2y = wgs_to_tile(x2, y2, z)
    print("pos1x, pos1y:", pos1x, pos1y)
    print("pos2x, pos2y:", pos2x, pos2y)
    lenx = abs(pos2x - pos1x) + 1
    leny = abs(pos2y - pos1y) + 1
    print("Total tiles number:{x} X {y}".format(x=lenx, y=leny))
    print("pos1y, pos1y + leny:", pos1y, pos1y + leny)
    print("pos1x, pos1x + lenx:", pos1x, pos1x + lenx)
    urls = [get_url(source, i, j, z, style) for j in range(pos1y, pos1y + leny) for i in range(pos1x, pos1x + lenx)]
    return urls


# ---------------------------------------------------------

# ---------------------------------------------------------
def merge_tiles(datas, x1, y1, x2, y2, z):
    pos1x, pos1y = wgs_to_tile(x1, y1, z)
    pos2x, pos2y = wgs_to_tile(x2, y2, z)
    lenx = pos2x - pos1x + 1
    leny = pos2y - pos1y + 1
    outpic = pil.new('RGBA', (lenx * 256, leny * 256))
    for i, data in enumerate(datas):
        picio = io.BytesIO(data)
        small_pic = pil.open(picio)
        y, x = i // lenx, i % lenx
        outpic.paste(small_pic, (x * 256, y * 256))
    print('Tiles merge completed')
    return outpic


def download_tiles(urls, multi=1):
    url_len = len(urls)
    # datas = [None] * url_len
    datas = dict()
    if multi < 1 or multi > 20 or not isinstance(multi, int):
        raise Exception("multi of Downloader shuold be int and between 1 to 20.")
    tasks = [Downloader(i, multi, urls, datas) for i in range(multi)]
    
    for i in tasks:
        i.start()
    for i in tasks:
        i.join()
    return datas


# ---------------------------------------------------------

# ---------------------------------------------------------
def main(left, top, right, bottom, zoom, filePath, style='s', server="Google China", zone_id=""):
    """
    Download images based on spatial extent.

    East longitude is positive and west longitude is negative.
    North latitude is positive, south latitude is negative.

    Parameters
    ----------
    left, top : left-top coordinate, for example (100.361,38.866)
        
    right, bottom : right-bottom coordinate
        
    z : zoom

    filePath : File path for storing results, TIFF format
        
    style : 
        m for map; 
        s for satellite; 
        y for satellite with label; 
        t for terrain; 
        p for terrain with label; 
        h for label;
    
    source : Google China (default) or Google
    """
    # ---------------------------------------------------------
    # Get the urls of all tiles in the extent
    urls = get_urls(left, top, right, bottom, zoom, server, style)
    print("瓦片图总数:", len(urls))

    # Group URLs based on the number of CPU cores to achieve roughly equal amounts of tasks
    urls_group = [urls[i:i + math.ceil(len(urls) / 2)] for i in
                  range(0, len(urls), math.ceil(len(urls) / 2))]
    # urls_group = [urls[i:i + math.ceil(len(urls) / multiprocessing.cpu_count())] for i in
    #               range(0, len(urls), math.ceil(len(urls) / multiprocessing.cpu_count()))]
    print(urls_group)
    # return False
    # Each set of URLs corresponds to a process for downloading tile maps
    print('Tiles downloading......瓦片图下载中')
    # results = []
    pool = multiprocessing.Pool(2)
    # pool = multiprocessing.Pool(multiprocessing.cpu_count())
    print("cpu_count:", multiprocessing.cpu_count())
    print("pool", pool)
    results = pool.map(download_tiles, urls_group)
    pool.close()
    pool.join()
    # print("results:", type(results[0]))
    image_number = 1
    for res in results:
        for key in res.keys():
            print(f"开始保存第{image_number}张图片")
            print("image_name:", key)
            x = str(key).split("_")[0]
            y = str(key).split("_")[1]
            parent_dir = Path(f"D:\\tiles\\{zone_id}\\tiles\\{zoom}\\{x}")  # 父级目录
            if not parent_dir.exists():
                parent_dir.mkdir(exist_ok=True, parents=True)
            with open(f"D:\\tiles\\{zone_id}\\tiles\\{zoom}\\{x}\\{y}.png", "wb") as f:
                # print(_)
                print("sys.getsizeof(res[key]):", sys.getsizeof(res[key]))
                f.write(res[key])
            image_number += 1
        # print(res.get())
    # result = [x for j in results for x in j]
    print('Tiles download complete 瓦片图 下载成功')
    #     # break

    # Combine downloaded tile maps into one map
    # outpic = merge_tiles(result, left, top, right, bottom, zoom)
    # outpic = outpic.convert('RGB')
    # r, g, b = cv2.split(np.array(outpic))

    # # Get the spatial information of the four corners of the merged map and use it for outputting
    # extent = getExtent(left, top, right, bottom, zoom, server)
    # gt = (extent['LT'][0], (extent['RB'][0] - extent['LT'][0]) / r.shape[1], 0, extent['LT'][1], 0,
    #       (extent['RB'][1] - extent['LT'][1]) / r.shape[0])
    # saveTiff(r, g, b, gt, filePath)


# ---------------------------------------------------------
if __name__ == '__main__':
    from sqls import select_zone_info
    import json
    start_time = time.time()
    zone_id = "1547212382202232832"
    # main(118.901, 32.066,118.934, 32.109, 18, r'.\Temp\test.tif', server="Google China")
    # main(100.361, 38.866, 100.386, 38.839, 18, r'.\Temp\test.tif', server="Google China")
    # main(97.376955,37.133309,97.4074156,37.118448, 5, r'.\Temp\test-one.tif', server="onwer_server", style="onwer_server_AREA:onwer_server_super_hd")
    # path = [{"lng": 118.909207, "lat": 32.081909}, {"lng": 118.912005, "lat": 32.081703}, {"lng": 118.911776, "lat": 32.081512}, {"lng": 118.909180, "lat": 32.080421}]
    left_top_x, left_top_y, right_buttom_x, right_buttom_y = get_zone_gps_max(zone_id)
    # print(float(left_top[0]), float(left_top[1]), float(right_buttom[0]), float(right_buttom[1]))
    for zoom in range(18, 19):
        parent_dir = Path(f"D:\\tiles\\{zone_id}\\tiles\\{zoom}")  # 父级目录
        if not parent_dir.exists():
            parent_dir.mkdir(exist_ok=True, parents=True)
        if 1:
            main(float(left_top_x),float(left_top_y),float(right_buttom_x),float(right_buttom_y), zoom, r'.\\Temp\\test-one.tif', server="onwer_server", style="onwer_server_AREA:onwer_server_super_hd", zone_id=zone_id)
            #main(97.376955,37.133309,97.376955,37.133309, 19, r'.\Temp\test-one.tif', server="Google China")
            end_time = time.time()
            print('lasted a total of {:.2f} seconds'.format(end_time - start_time))

2.合并瓦片图  谷歌的瓦片图长这样.瓦片图是金字塔类型的,这里就不多做解释了.

获取瓦片左上角坐标的python代码 python瓦片地图_Image_02

 合并代码  就是创建一个大的画布,然后把下载好的瓦片图一张张的贴上去,没有难度

import glob
import re
from PIL import Image

files = glob.glob('D:\\tiles\\1547212382202232832\\tiles\\17\\*\\*.png')
# print(re.findall(r"\d+", files[0])[-2:])
# print(tuple(int(i) for i in re.findall(r"\d+", files[0])[-2:]))
files.sort(key=lambda x: tuple(int(i) for i in re.findall(r"\d+", x)[-2:]))
# print(files)
imagefiles = {}
for item in files:
    match = re.findall(r'\d+', item)
    # print(match[-2])
    pre = int(match[-2])
    if not imagefiles.get(pre):
        imagefiles[pre] = []
    imagefiles[pre].append(item)

imagefiles = sorted(zip(imagefiles.keys(), imagefiles.values()))
print(imagefiles)
total_width = len(imagefiles) * 256
total_height = len(imagefiles[0][1]) * 256

new_image = Image.new('RGB', (total_width, total_height))

x_offset = 0
for item in imagefiles:
    y_offset = 0
    # print(item[1])
    images = map(Image.open, item[1])
    # print(list(images))
    for subitem in images:
        new_image.paste(subitem, (x_offset, y_offset))
        y_offset += subitem.size[0]
        _x = subitem.size[0]
    # print(list(images))
    x_offset += _x

new_image.save('merge.jpg', quality=90)

3.在合并好的瓦片图上绘制我们的mark点和多边形。

思路:首先我们合并好的瓦片图上只有像素一个计量单位,如果要化gps点上去的话,就要找到一个全局的参考坐标。最好的参考坐标就是左上角第一块瓦片。找到左上角的点坐标。因为切出来的瓦片像素是我们自定义的,我用的是256*256,同时可以获取到瓦片的实际长度和宽度(就是bbox参数/墨卡托投影),由此我们可以算出单位像素对应的实际长度(单位是米)。   接下来我们只需要找到左上角第一块瓦片的左上角的点坐标即可。   通过左上角瓦片图的gps可以算出对应的瓦片图坐标,根据瓦片图坐标既可以算出 瓦片的左下角坐标和右上角坐标,既得左上角坐标。再转成墨卡托投影即可作为全局的参考坐标了。 ps:瓦片的范围用最小外接矩形来算。

def get_zone_gps_max(zone_id):
    path_info = select_zone_info(zone_id)
    path = json.loads(path_info[0]["path"])
    path = [list(WGS84_to_WebMercator(_["lng"], _["lat"])) for _ in path]
    print(path)
    # left_top, right_buttom = get_maxarea_box(path)
    min_x, max_x, min_y, max_y = get_maxarea_box(path)
    print("右下→左下→左上→右上:", min_x, max_x, min_y, max_y)
    left_top_x, left_top_y = WebMercator_to_WGS84(min_x, max_y)
    right_buttom_x, right_buttom_y = WebMercator_to_WGS84(max_x, min_y)
    return (float(left_top_x), float(left_top_y), float(right_buttom_x), float(right_buttom_y))



def get_first_tile_poi(zone_id, z=17):
    """
    @description  :获取第一块瓦片图的坐标
    ---------
    @param  :
    -------
    @Returns  :左下角和右上角
    -------
    """
    left_top_x, left_top_y, right_buttom_x, right_buttom_y = get_zone_gps_max(zone_id)
    pos1x, pos1y = wgs_to_tile(left_top_x, left_top_y, z)  # 左上角的瓦片图坐标
    min_xy_list = tile2lonlat(pos1x, pos1y + 1, z)
    max_xy_list = tile2lonlat(pos1x + 1, pos1y, z)
    lng_min, lat_min = min_xy_list[0], min_xy_list[1]
    lng_max, lat_max = max_xy_list[0], max_xy_list[1]
    # gcj02转wgs84
    # lng_min, lat_min = GCJ02_to_WGS84(min_xy_list[0], min_xy_list[1])
    lng_min, lat_min = WGS84_to_WebMercator(lng_min, lat_min)
    # lng_max, lat_max = GCJ02_to_WGS84(max_xy_list[0], max_xy_list[1])
    lng_max, lat_max = WGS84_to_WebMercator(lng_max, lat_max)
    bbox = [lng_min, lat_min, lng_max, lat_max]
    return bbox

有疑问或者错误的地方 可以留言讨论 互相学习