[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 zhengjie9510\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Google-Map-Downloader\n![Google-Map-Downloader](https://geospatialmedia.s3.amazonaws.com/wp-content/uploads/2016/07/google-earth.jpg)\n## 中文：  \n[downloader_1.1](https://github.com/zhengjie9510/Google-Map-Downloader/blob/master/downloader_1.1.py)：  \n    一个小工具，你只需要输入空间范围、地图缩放等级就可以实现Google地图的下载，并输出为TIFF格式，含空间坐标系。  \n[downloader_1.2](https://github.com/zhengjie9510/Google-Map-Downloader/blob/master/downloader_1.2.py)：  \n    是在1.1版本上的改进。由于python的多线程中存在GIL锁，导致python的多线程不能利用多核，考虑到现在的计算机是多核的，为了充分利用计算机的多核资源，提高下载速度，尝试利用多进程+多线程的方式来实现地图切片下载，最终速度得到极大提高。但该部分还没有实现进度条功能。  \n## English:  \n[downloader_1.1](https://github.com/zhengjie9510/Google-Map-Downloader/blob/master/downloader_1.1.py):  \n    A small tool, you only need to input the spatial extent and map zoom level to download Google Maps, and output to TIFF format, including the spatial coordinate system.  \n[downloader_1.2](https://github.com/zhengjie9510/Google-Map-Downloader/blob/master/downloader_1.2.py):  \n    It is an improvement on version 1.1. Due to the existence of GIL locks in python's multi-threading, python's multi-threading cannot use multi-cores. Considering that computers are now multi-core, In order to make full use of the computer's multi-core resources and increase the download speed, try to use multi-process + multi-threaded way to achieve map tile download. The final speed has been greatly improved, but this part has not implemented the progress bar function.\n## 指南/Guide\n### 安装/Install\n```python\nconda install --yes --file requirements.txt\n```\n### 使用/Use\n```python\nif __name__ == '__main__':\n    start_time=time.time()\n    \n    # main(100.361,38.866,100.386,38.839,13,r'C:\\Users\\test.tif')\n    main(left,top,right,bottom,zoom,filePath,style='s',server=\"Google\")\n\n    end_time=time.time()\n    print('lasted a total of {:.2f} seconds'.format(end_time-start_time))\n```\n```python\n'''\nParameters\n----------\nleft, top : left-top coordinate, for example (100.361,38.866)\n    \nright, bottom : right-bottom coordinate\n    \nz : zoom\n\nfilePath : File path for storing results, TIFF format\n    \nstyle : \n    m for map; \n    s for satellite; \n    y for satellite with label; \n    t for terrain; \n    p for terrain with label; \n    h for label;\n\nsource : Google China (default) or Google\n'''\n```\n## 问题/Issues\nIf you encounter the problem of Bad network link, you can change the HEADERS in the download function, and try again.\n```python\ndef download(self,url):\n        HEADERS = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36'}\n        header = ur.Request(url,headers=HEADERS)\n        err=0\n        while(err<3):\n            try:\n                data = ur.urlopen(header).read()\n            except:\n                err+=1\n            else:\n                return data\n        raise Exception(\"Bad network link.\")\n```"
  },
  {
    "path": "downloader_1.1.py",
    "content": "# -*- coding: utf-8 -*-\n'''\nThis code is used to download image from google\n\n@date  : 2020-3-10\n@author: Zheng Jie\n@E-mail: zhengjie9510@qq.com\n'''\n\nimport io\nimport math\nimport numpy as np\nfrom math import floor, pi, log, tan, atan, exp\nfrom threading import Thread, Lock\nimport urllib.request as ur\nimport PIL.Image as pil\n\nimport cv2\nfrom osgeo import gdal, osr\nimport time\n\n\n# ------------------Interchange between WGS-84 and Web Mercator-------------------------\n# WGS-84 to Web Mercator\ndef wgs_to_mercator(x, y):\n    y = 85.0511287798 if y > 85.0511287798 else y\n    y = -85.0511287798 if y < -85.0511287798 else y\n\n    x2 = x * 20037508.34 / 180\n    y2 = log(tan((90 + y) * pi / 360)) / (pi / 180)\n    y2 = y2 * 20037508.34 / 180\n    return x2, y2\n\n\n# Web Mercator to WGS-84\ndef mercator_to_wgs(x, y):\n    x2 = x / 20037508.34 * 180\n    y2 = y / 20037508.34 * 180\n    y2 = 180 / pi * (2 * atan(exp(y2 * pi / 180)) - pi / 2)\n    return x2, y2\n\n\n# -------------------------------------------------------------\n\n# -----------------Interchange between GCJ-02 to WGS-84---------------------------\n# All public geographic data in mainland China need to be encrypted with GCJ-02, introducing random bias\n# This part of the code is used to remove the bias\ndef transformLat(x, y):\n    ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))\n    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0\n    ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0\n    ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0\n    return ret\n\n\ndef transformLon(x, y):\n    ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))\n    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0\n    ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0\n    ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0\n    return ret\n\n\ndef delta(lat, lon):\n    ''' \n    Krasovsky 1940\n    //\n    // a = 6378245.0, 1/f = 298.3\n    // b = a * (1 - f)\n    // ee = (a^2 - b^2) / a^2;\n    '''\n    a = 6378245.0  # a: Projection factor of satellite ellipsoidal coordinates projected onto a flat map coordinate system\n    ee = 0.00669342162296594323  # ee: Eccentricity of ellipsoid\n    dLat = transformLat(lon - 105.0, lat - 35.0)\n    dLon = transformLon(lon - 105.0, lat - 35.0)\n    radLat = lat / 180.0 * math.pi\n    magic = math.sin(radLat)\n    magic = 1 - ee * magic * magic\n    sqrtMagic = math.sqrt(magic)\n    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * math.pi)\n    dLon = (dLon * 180.0) / (a / sqrtMagic * math.cos(radLat) * math.pi)\n    return {'lat': dLat, 'lon': dLon}\n\n\ndef outOfChina(lat, lon):\n    if (lon < 72.004 or lon > 137.8347):\n        return True\n    if (lat < 0.8293 or lat > 55.8271):\n        return True\n    return False\n\n\ndef gcj_to_wgs(gcjLon, gcjLat):\n    if outOfChina(gcjLat, gcjLon):\n        return (gcjLon, gcjLat)\n    d = delta(gcjLat, gcjLon)\n    return (gcjLon - d[\"lon\"], gcjLat - d[\"lat\"])\n\n\ndef wgs_to_gcj(wgsLon, wgsLat):\n    if outOfChina(wgsLat, wgsLon):\n        return wgsLon, wgsLat\n    d = delta(wgsLat, wgsLon);\n    return wgsLon + d[\"lon\"], wgsLat + d[\"lat\"]\n\n\n# --------------------------------------------------------------\n\n# ---------------------------------------------------------\n# Get tile coordinates in Google Maps based on latitude and longitude of WGS-84\ndef wgs_to_tile(j, w, z):\n    '''\n    Get google-style tile cooridinate from geographical coordinate\n    j : Longittude\n    w : Latitude\n    z : zoom\n    '''\n    isnum = lambda x: isinstance(x, int) or isinstance(x, float)\n    if not (isnum(j) and isnum(w)):\n        raise TypeError(\"j and w must be int or float!\")\n\n    if not isinstance(z, int) or z < 0 or z > 22:\n        raise TypeError(\"z must be int and between 0 to 22.\")\n\n    if j < 0:\n        j = 180 + j\n    else:\n        j += 180\n    j /= 360  # make j to (0,1)\n\n    w = 85.0511287798 if w > 85.0511287798 else w\n    w = -85.0511287798 if w < -85.0511287798 else w\n    w = log(tan((90 + w) * pi / 360)) / (pi / 180)\n    w /= 180  # make w to (-1,1)\n    w = 1 - (w + 1) / 2  # make w to (0,1) and left top is 0-point\n\n    num = 2 ** z\n    x = floor(j * num)\n    y = floor(w * num)\n    return x, y\n\n\ndef pixls_to_mercator(zb):\n    # Get the web Mercator projection coordinates of the four corners of the area according to the four corner coordinates of the tile\n    inx, iny = zb[\"LT\"]  # left top\n    inx2, iny2 = zb[\"RB\"]  # right bottom\n    length = 20037508.3427892\n    sum = 2 ** zb[\"z\"]\n    LTx = inx / sum * length * 2 - length\n    LTy = -(iny / sum * length * 2) + length\n\n    RBx = (inx2 + 1) / sum * length * 2 - length\n    RBy = -((iny2 + 1) / sum * length * 2) + length\n\n    # LT=left top,RB=right buttom\n    # Returns the projected coordinates of the four corners\n    res = {'LT': (LTx, LTy), 'RB': (RBx, RBy),\n           'LB': (LTx, RBy), 'RT': (RBx, LTy)}\n    return res\n\n\ndef tile_to_pixls(zb):\n    # Tile coordinates are converted to pixel coordinates of the four corners\n    out = {}\n    width = (zb[\"RT\"][0] - zb[\"LT\"][0] + 1) * 256\n    height = (zb[\"LB\"][1] - zb[\"LT\"][1] + 1) * 256\n    out[\"LT\"] = (0, 0)\n    out[\"RT\"] = (width, 0)\n    out[\"LB\"] = (0, -height)\n    out[\"RB\"] = (width, -height)\n    return out\n\n\n# -----------------------------------------------------------\n\n# ---------------------------------------------------------\nclass Downloader(Thread):\n    # multiple threads downloader\n    def __init__(self, index, count, urls, datas, update):\n        # index represents the number of threads\n        # count represents the total number of threads\n        # urls represents the list of URLs nedd to be downloaded\n        # datas represents the list of data need to be returned.\n        super().__init__()\n        self.urls = urls\n        self.datas = datas\n        self.index = index\n        self.count = count\n        self.update = update\n\n    def download(self, url):\n        HEADERS = {\n            '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'}\n        header = ur.Request(url, headers=HEADERS)\n        err = 0\n        while (err < 3):\n            try:\n                data = ur.urlopen(header).read()\n            except:\n                err += 1\n            else:\n                return data\n        raise Exception(\"Bad network link.\")\n\n    def run(self):\n        for i, url in enumerate(self.urls):\n            if i % self.count != self.index:\n                continue\n            self.datas[i] = self.download(url)\n            if mutex.acquire():\n                self.update()\n                mutex.release()\n\n\n# ---------------------------------------------------------\n\n# ---------------------------------------------------------\nMAP_URLS = {\n    \"Google\": \"http://mts0.googleapis.com/vt?lyrs={style}&x={x}&y={y}&z={z}\",\n    \"Google China\": \"http://mt2.google.cn/vt/lyrs={style}&hl=zh-CN&gl=CN&src=app&x={x}&y={y}&z={z}\"}\n\n\ndef get_url(source, x, y, z, style):  #\n    if source == 'Google China':\n        url = MAP_URLS[\"Google China\"].format(x=x, y=y, z=z, style=style)\n    elif source == 'Google':\n        url = MAP_URLS[\"Google\"].format(x=x, y=y, z=z, style=style)\n    else:\n        raise Exception(\"Unknown Map Source ! \")\n    return url\n\n\ndef get_urls(x1, y1, x2, y2, z, source='google', style='s'):\n    pos1x, pos1y = wgs_to_tile(x1, y1, z)\n    pos2x, pos2y = wgs_to_tile(x2, y2, z)\n    lenx = pos2x - pos1x + 1\n    leny = pos2y - pos1y + 1\n    print(\"Total tiles number：{x} X {y}\".format(x=lenx, y=leny))\n    urls = [get_url(source, i, j, z, style) for j in range(pos1y, pos1y + leny) for i in range(pos1x, pos1x + lenx)]\n    return urls\n\n\n# ---------------------------------------------------------\n\n# ---------------------------------------------------------\ndef download_tiles(urls, multi=10):\n    def makeupdate(s):\n        def up():\n            global COUNT\n            COUNT += 1\n            print(\"\\rDownLoading...[{0}/{1}]\".format(COUNT, s), end='')\n\n        return up\n\n    url_len = len(urls)\n    datas = [None] * url_len\n    if multi < 1 or multi > 20 or not isinstance(multi, int):\n        raise Exception(\"multi of Downloader shuold be int and between 1 to 20.\")\n    tasks = [Downloader(i, multi, urls, datas, makeupdate(url_len)) for i in range(multi)]\n    for i in tasks:\n        i.start()\n    for i in tasks:\n        i.join()\n    return datas\n\n\ndef merge_tiles(datas, x1, y1, x2, y2, z):\n    pos1x, pos1y = wgs_to_tile(x1, y1, z)\n    pos2x, pos2y = wgs_to_tile(x2, y2, z)\n    lenx = pos2x - pos1x + 1\n    leny = pos2y - pos1y + 1\n    outpic = pil.new('RGBA', (lenx * 256, leny * 256))\n    for i, data in enumerate(datas):\n        picio = io.BytesIO(data)\n        small_pic = pil.open(picio)\n\n        y, x = i // lenx, i % lenx\n        outpic.paste(small_pic, (x * 256, y * 256))\n    print('\\nTiles merge completed')\n    return outpic\n\n\n# ---------------------------------------------------------\n\n# ---------------------------------------------------------\ndef getExtent(x1, y1, x2, y2, z, source=\"Google China\"):\n    pos1x, pos1y = wgs_to_tile(x1, y1, z)\n    pos2x, pos2y = wgs_to_tile(x2, y2, z)\n    Xframe = pixls_to_mercator(\n        {\"LT\": (pos1x, pos1y), \"RT\": (pos2x, pos1y), \"LB\": (pos1x, pos2y), \"RB\": (pos2x, pos2y), \"z\": z})\n    for i in [\"LT\", \"LB\", \"RT\", \"RB\"]:\n        Xframe[i] = mercator_to_wgs(*Xframe[i])\n    if source == \"Google\":\n        pass\n    elif source == \"Google China\":\n        for i in [\"LT\", \"LB\", \"RT\", \"RB\"]:\n            Xframe[i] = gcj_to_wgs(*Xframe[i])\n    else:\n        raise Exception(\"Invalid argument: source.\")\n    return Xframe\n\n\ndef saveTiff(r, g, b, gt, filePath):\n    fname_out = filePath\n    driver = gdal.GetDriverByName('GTiff')\n    # Create a 3-band dataset\n    dset_output = driver.Create(fname_out, r.shape[1], r.shape[0], 3, gdal.GDT_Byte)\n    dset_output.SetGeoTransform(gt)\n    try:\n        proj = osr.SpatialReference()\n        proj.ImportFromEPSG(4326)\n        dset_output.SetSpatialRef(proj)\n    except:\n        print(\"Error: Coordinate system setting failed\")\n    dset_output.GetRasterBand(1).WriteArray(r)\n    dset_output.GetRasterBand(2).WriteArray(g)\n    dset_output.GetRasterBand(3).WriteArray(b)\n    dset_output.FlushCache()\n    dset_output = None\n    print(\"Image Saved\")\n\n\n# ---------------------------------------------------------\n\ndef main(left, top, right, bottom, zoom, filePath, style='s', server=\"Google China\"):\n    \"\"\"\n    Download images based on spatial extent.\n\n    East longitude is positive and west longitude is negative.\n    North latitude is positive, south latitude is negative.\n\n    Parameters\n    ----------\n    left, top : left-top coordinate, for example (100.361,38.866)\n        \n    right, bottom : right-bottom coordinate\n        \n    z : zoom\n\n    filePath : File path for storing results, TIFF format\n        \n    style : \n        m for map; \n        s for satellite; \n        y for satellite with label; \n        t for terrain; \n        p for terrain with label; \n        h for label;\n    \n    source : Google China (default) or Google\n    \"\"\"\n    # ---------------------------------------------------------\n    # Get the urls of all tiles in the extent\n    urls = get_urls(left, top, right, bottom, zoom, server, style)\n\n    # Download tiles\n    datas = download_tiles(urls)\n\n    # Combine downloaded tile maps into one map\n    outpic = merge_tiles(datas, left, top, right, bottom, zoom)\n    outpic = outpic.convert('RGB')\n    r, g, b = cv2.split(np.array(outpic))\n\n    # Get the spatial information of the four corners of the merged map and use it for outputting\n    extent = getExtent(left, top, right, bottom, zoom, server)\n    gt = (extent['LT'][0], (extent['RB'][0] - extent['LT'][0]) / r.shape[1], 0, extent['LT'][1], 0,\n          (extent['RB'][1] - extent['LT'][1]) / r.shape[0])\n    saveTiff(r, g, b, gt, filePath)\n\n\n# ---------------------------------------------------------\nif __name__ == '__main__':\n    start_time = time.time()\n\n    COUNT = 0  # Progress display, starting at 0\n    mutex = Lock()\n    main(100.361, 38.866, 100.386, 38.839, 13, r'D:\\Documents\\Temp\\test1.tif', server=\"Google\")\n\n    end_time = time.time()\n    print('lasted a total of {:.2f} seconds'.format(end_time - start_time))\n"
  },
  {
    "path": "downloader_1.2.py",
    "content": "# -*- coding: utf-8 -*\n'''\nThis code is used to download image from google\n\n@date  : 2020-3-13\n@author: Zheng Jie\n@E-mail: zhengjie9510@qq.com\n'''\n\nimport io\nimport math\nimport multiprocessing\nimport time\nimport urllib.request as ur\nfrom math import floor, pi, log, tan, atan, exp\nfrom threading import Thread\n\nimport PIL.Image as pil\nimport cv2\nimport numpy as np\nfrom osgeo import gdal, osr\n\n\n# ------------------Interchange between WGS-84 and Web Mercator-------------------------\n# WGS-84 to Web Mercator\ndef wgs_to_mercator(x, y):\n    y = 85.0511287798 if y > 85.0511287798 else y\n    y = -85.0511287798 if y < -85.0511287798 else y\n\n    x2 = x * 20037508.34 / 180\n    y2 = log(tan((90 + y) * pi / 360)) / (pi / 180)\n    y2 = y2 * 20037508.34 / 180\n    return x2, y2\n\n\n# Web Mercator to WGS-84\ndef mercator_to_wgs(x, y):\n    x2 = x / 20037508.34 * 180\n    y2 = y / 20037508.34 * 180\n    y2 = 180 / pi * (2 * atan(exp(y2 * pi / 180)) - pi / 2)\n    return x2, y2\n\n\n# --------------------------------------------------------------------------------------\n\n# -----------------Interchange between GCJ-02 to WGS-84---------------------------\n# All public geographic data in mainland China need to be encrypted with GCJ-02, introducing random bias\n# This part of the code is used to remove the bias\ndef transformLat(x, y):\n    ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))\n    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0\n    ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0\n    ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0\n    return ret\n\n\ndef transformLon(x, y):\n    ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))\n    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0\n    ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0\n    ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0\n    return ret\n\n\ndef delta(lat, lon):\n    ''' \n    Krasovsky 1940\n    //\n    // a = 6378245.0, 1/f = 298.3\n    // b = a * (1 - f)\n    // ee = (a^2 - b^2) / a^2;\n    '''\n    a = 6378245.0  # a: Projection factor of satellite ellipsoidal coordinates projected onto a flat map coordinate system\n    ee = 0.00669342162296594323  # ee: Eccentricity of ellipsoid\n    dLat = transformLat(lon - 105.0, lat - 35.0)\n    dLon = transformLon(lon - 105.0, lat - 35.0)\n    radLat = lat / 180.0 * math.pi\n    magic = math.sin(radLat)\n    magic = 1 - ee * magic * magic\n    sqrtMagic = math.sqrt(magic)\n    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * math.pi)\n    dLon = (dLon * 180.0) / (a / sqrtMagic * math.cos(radLat) * math.pi)\n    return {'lat': dLat, 'lon': dLon}\n\n\ndef outOfChina(lat, lon):\n    if (lon < 72.004 or lon > 137.8347):\n        return True\n    if (lat < 0.8293 or lat > 55.8271):\n        return True\n    return False\n\n\ndef gcj_to_wgs(gcjLon, gcjLat):\n    if outOfChina(gcjLat, gcjLon):\n        return (gcjLon, gcjLat)\n    d = delta(gcjLat, gcjLon)\n    return (gcjLon - d[\"lon\"], gcjLat - d[\"lat\"])\n\n\ndef wgs_to_gcj(wgsLon, wgsLat):\n    if outOfChina(wgsLat, wgsLon):\n        return wgsLon, wgsLat\n    d = delta(wgsLat, wgsLon)\n    return wgsLon + d[\"lon\"], wgsLat + d[\"lat\"]\n\n\n# --------------------------------------------------------------\n\n# ---------------------------------------------------------\n# Get tile coordinates in Google Maps based on latitude and longitude of WGS-84\ndef wgs_to_tile(j, w, z):\n    '''\n    Get google-style tile cooridinate from geographical coordinate\n    j : Longittude\n    w : Latitude\n    z : zoom\n    '''\n    isnum = lambda x: isinstance(x, int) or isinstance(x, float)\n    if not (isnum(j) and isnum(w)):\n        raise TypeError(\"j and w must be int or float!\")\n\n    if not isinstance(z, int) or z < 0 or z > 22:\n        raise TypeError(\"z must be int and between 0 to 22.\")\n\n    if j < 0:\n        j = 180 + j\n    else:\n        j += 180\n    j /= 360  # make j to (0,1)\n\n    w = 85.0511287798 if w > 85.0511287798 else w\n    w = -85.0511287798 if w < -85.0511287798 else w\n    w = log(tan((90 + w) * pi / 360)) / (pi / 180)\n    w /= 180  # make w to (-1,1)\n    w = 1 - (w + 1) / 2  # make w to (0,1) and left top is 0-point\n\n    num = 2 ** z\n    x = floor(j * num)\n    y = floor(w * num)\n    return x, y\n\n\ndef pixls_to_mercator(zb):\n    # Get the web Mercator projection coordinates of the four corners of the area according to the four corner coordinates of the tile\n    inx, iny = zb[\"LT\"]  # left top\n    inx2, iny2 = zb[\"RB\"]  # right bottom\n    length = 20037508.3427892\n    sum = 2 ** zb[\"z\"]\n    LTx = inx / sum * length * 2 - length\n    LTy = -(iny / sum * length * 2) + length\n\n    RBx = (inx2 + 1) / sum * length * 2 - length\n    RBy = -((iny2 + 1) / sum * length * 2) + length\n\n    # LT=left top,RB=right buttom\n    # Returns the projected coordinates of the four corners\n    res = {'LT': (LTx, LTy), 'RB': (RBx, RBy),\n           'LB': (LTx, RBy), 'RT': (RBx, LTy)}\n    return res\n\n\ndef tile_to_pixls(zb):\n    # Tile coordinates are converted to pixel coordinates of the four corners\n    out = {}\n    width = (zb[\"RT\"][0] - zb[\"LT\"][0] + 1) * 256\n    height = (zb[\"LB\"][1] - zb[\"LT\"][1] + 1) * 256\n    out[\"LT\"] = (0, 0)\n    out[\"RT\"] = (width, 0)\n    out[\"LB\"] = (0, -height)\n    out[\"RB\"] = (width, -height)\n    return out\n\n\n# -----------------------------------------------------------\n\n# ---------------------------------------------------------\nclass Downloader(Thread):\n    # multiple threads downloader\n    def __init__(self, index, count, urls, datas):\n        # index represents the number of threads\n        # count represents the total number of threads\n        # urls represents the list of URLs nedd to be downloaded\n        # datas represents the list of data need to be returned.\n        super().__init__()\n        self.urls = urls\n        self.datas = datas\n        self.index = index\n        self.count = count\n\n    def download(self, url):\n        HEADERS = {\n            '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'}\n        header = ur.Request(url, headers=HEADERS)\n        err = 0\n        while (err < 3):\n            try:\n                data = ur.urlopen(header).read()\n            except:\n                err += 1\n            else:\n                return data\n        raise Exception(\"Bad network link.\")\n\n    def run(self):\n        for i, url in enumerate(self.urls):\n            if i % self.count != self.index:\n                continue\n            self.datas[i] = self.download(url)\n\n\n# ---------------------------------------------------------\n\n# ---------------------------------------------------------\ndef getExtent(x1, y1, x2, y2, z, source=\"Google China\"):\n    pos1x, pos1y = wgs_to_tile(x1, y1, z)\n    pos2x, pos2y = wgs_to_tile(x2, y2, z)\n    Xframe = pixls_to_mercator(\n        {\"LT\": (pos1x, pos1y), \"RT\": (pos2x, pos1y), \"LB\": (pos1x, pos2y), \"RB\": (pos2x, pos2y), \"z\": z})\n    for i in [\"LT\", \"LB\", \"RT\", \"RB\"]:\n        Xframe[i] = mercator_to_wgs(*Xframe[i])\n    if source == \"Google\":\n        pass\n    elif source == \"Google China\":\n        for i in [\"LT\", \"LB\", \"RT\", \"RB\"]:\n            Xframe[i] = gcj_to_wgs(*Xframe[i])\n    else:\n        raise Exception(\"Invalid argument: source.\")\n    return Xframe\n\n\ndef saveTiff(r, g, b, gt, filePath):\n    fname_out = filePath\n    driver = gdal.GetDriverByName('GTiff')\n    # Create a 3-band dataset\n    dset_output = driver.Create(fname_out, r.shape[1], r.shape[0], 3, gdal.GDT_Byte)\n    dset_output.SetGeoTransform(gt)\n    try:\n        proj = osr.SpatialReference()\n        proj.ImportFromEPSG(4326)\n        dset_output.SetSpatialRef(proj)\n    except:\n        print(\"Error: Coordinate system setting failed\")\n    dset_output.GetRasterBand(1).WriteArray(r)\n    dset_output.GetRasterBand(2).WriteArray(g)\n    dset_output.GetRasterBand(3).WriteArray(b)\n    dset_output.FlushCache()\n    dset_output = None\n    print(\"Image Saved\")\n\n\n# ---------------------------------------------------------\n\n# ---------------------------------------------------------\nMAP_URLS = {\n    \"Google\": \"http://mts0.googleapis.com/vt?lyrs={style}&x={x}&y={y}&z={z}\",\n    \"Google China\": \"http://mt2.google.cn/vt/lyrs={style}&hl=zh-CN&gl=CN&src=app&x={x}&y={y}&z={z}\"}\n\n\ndef get_url(source, x, y, z, style):  #\n    if source == 'Google China':\n        url = MAP_URLS[\"Google China\"].format(x=x, y=y, z=z, style=style)\n    elif source == 'Google':\n        url = MAP_URLS[\"Google\"].format(x=x, y=y, z=z, style=style)\n    else:\n        raise Exception(\"Unknown Map Source ! \")\n    return url\n\n\ndef get_urls(x1, y1, x2, y2, z, source, style):\n    pos1x, pos1y = wgs_to_tile(x1, y1, z)\n    pos2x, pos2y = wgs_to_tile(x2, y2, z)\n    lenx = pos2x - pos1x + 1\n    leny = pos2y - pos1y + 1\n    print(\"Total tiles number：{x} X {y}\".format(x=lenx, y=leny))\n    urls = [get_url(source, i, j, z, style) for j in range(pos1y, pos1y + leny) for i in range(pos1x, pos1x + lenx)]\n    return urls\n\n\n# ---------------------------------------------------------\n\n# ---------------------------------------------------------\ndef merge_tiles(datas, x1, y1, x2, y2, z):\n    pos1x, pos1y = wgs_to_tile(x1, y1, z)\n    pos2x, pos2y = wgs_to_tile(x2, y2, z)\n    lenx = pos2x - pos1x + 1\n    leny = pos2y - pos1y + 1\n    outpic = pil.new('RGBA', (lenx * 256, leny * 256))\n    for i, data in enumerate(datas):\n        picio = io.BytesIO(data)\n        small_pic = pil.open(picio)\n        y, x = i // lenx, i % lenx\n        outpic.paste(small_pic, (x * 256, y * 256))\n    print('Tiles merge completed')\n    return outpic\n\n\ndef download_tiles(urls, multi=10):\n    url_len = len(urls)\n    datas = [None] * url_len\n    if multi < 1 or multi > 20 or not isinstance(multi, int):\n        raise Exception(\"multi of Downloader shuold be int and between 1 to 20.\")\n    tasks = [Downloader(i, multi, urls, datas) for i in range(multi)]\n    for i in tasks:\n        i.start()\n    for i in tasks:\n        i.join()\n    return datas\n\n\n# ---------------------------------------------------------\n\n# ---------------------------------------------------------\ndef main(left, top, right, bottom, zoom, filePath, style='s', server=\"Google China\"):\n    \"\"\"\n    Download images based on spatial extent.\n\n    East longitude is positive and west longitude is negative.\n    North latitude is positive, south latitude is negative.\n\n    Parameters\n    ----------\n    left, top : left-top coordinate, for example (100.361,38.866)\n        \n    right, bottom : right-bottom coordinate\n        \n    z : zoom\n\n    filePath : File path for storing results, TIFF format\n        \n    style : \n        m for map; \n        s for satellite; \n        y for satellite with label; \n        t for terrain; \n        p for terrain with label; \n        h for label;\n    \n    source : Google China (default) or Google\n    \"\"\"\n    # ---------------------------------------------------------\n    # Get the urls of all tiles in the extent\n    urls = get_urls(left, top, right, bottom, zoom, server, style)\n\n    # Group URLs based on the number of CPU cores to achieve roughly equal amounts of tasks\n    urls_group = [urls[i:i + math.ceil(len(urls) / multiprocessing.cpu_count())] for i in\n                  range(0, len(urls), math.ceil(len(urls) / multiprocessing.cpu_count()))]\n\n    # Each set of URLs corresponds to a process for downloading tile maps\n    print('Tiles downloading......')\n    pool = multiprocessing.Pool(multiprocessing.cpu_count())\n    results = pool.map(download_tiles, urls_group)\n    pool.close()\n    pool.join()\n    result = [x for j in results for x in j]\n    print('Tiles download complete')\n\n    # Combine downloaded tile maps into one map\n    outpic = merge_tiles(result, left, top, right, bottom, zoom)\n    outpic = outpic.convert('RGB')\n    r, g, b = cv2.split(np.array(outpic))\n\n    # Get the spatial information of the four corners of the merged map and use it for outputting\n    extent = getExtent(left, top, right, bottom, zoom, server)\n    gt = (extent['LT'][0], (extent['RB'][0] - extent['LT'][0]) / r.shape[1], 0, extent['LT'][1], 0,\n          (extent['RB'][1] - extent['LT'][1]) / r.shape[0])\n    saveTiff(r, g, b, gt, filePath)\n\n\n# ---------------------------------------------------------\nif __name__ == '__main__':\n    start_time = time.time()\n\n    main(100.361, 38.866, 100.386, 38.839, 13, r'D:\\Documents\\Temp\\test.tif', server=\"Google\")\n\n    end_time = time.time()\n    print('lasted a total of {:.2f} seconds'.format(end_time - start_time))\n"
  },
  {
    "path": "requirements.txt",
    "content": "GDAL==3.2.1\nnumpy==1.20.1\nPillow==10.0.1\npy-opencv==4.5.1\n"
  }
]