Guest User

Untitled

a guest
Dec 25th, 2025
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.45 KB | None | 0 0
  1. import argparse
  2. import io
  3. import math
  4. import ssl
  5. import urllib.error
  6. import urllib.parse
  7. import urllib.request
  8. import xml.etree.ElementTree as ET
  9.  
  10. from PIL import Image
  11.  
  12.  
  13. def zoomify_tiers(width: int, height: int, tile: int):
  14.     """Return tier tile counts and tile index offsets for Zoomify layout."""
  15.     tiers = []
  16.     edge = tile
  17.     while width > edge or height > edge:
  18.         tiers.append((math.ceil(width / edge), math.ceil(height / edge)))
  19.         edge *= 2
  20.     tiers.append((1, 1))
  21.     tiers.reverse()
  22.  
  23.     offsets = [0]
  24.     for i in range(1, len(tiers)):
  25.         offsets.append(tiers[i - 1][0] * tiers[i - 1][1] + offsets[i - 1])
  26.  
  27.     return tiers, offsets
  28.  
  29.  
  30. def normalize_base_url(raw_url: str) -> str:
  31.     """Turn a mapy.mzk.cz URL into the imageserver base tile URL."""
  32.     parsed = urllib.parse.urlparse(raw_url)
  33.     scheme = parsed.scheme or "https"
  34.     netloc = parsed.netloc
  35.     path = parsed.path
  36.  
  37.     if netloc == "mapy.mzk.cz":
  38.         netloc = "imageserver.mzk.cz"
  39.         scheme = "https"
  40.  
  41.     if path.endswith("ImageProperties.xml"):
  42.         path = path[: -len("ImageProperties.xml")]
  43.  
  44.     if not path.endswith("/"):
  45.         path = f"{path}/"
  46.  
  47.     if not netloc:
  48.         raise ValueError("URL must include a hostname")
  49.  
  50.     return urllib.parse.urlunparse((scheme, netloc, path, "", "", ""))
  51.  
  52.  
  53. def fetch_image_properties(base_url: str):
  54.     """Fetch Zoomify metadata (width, height, tile size) from the server.
  55.  
  56.    Falls back to HTTP if HTTPS negotiation fails (common on some mobile envs).
  57.    Returns metadata plus the effective base URL actually used.
  58.    """
  59.     meta_url = urllib.parse.urljoin(base_url, "ImageProperties.xml")
  60.     try:
  61.         with urllib.request.urlopen(meta_url) as resp:
  62.             xml_data = resp.read()
  63.         effective_base = base_url
  64.     except (ssl.SSLError, urllib.error.URLError):
  65.         if base_url.startswith("https://"):
  66.             fallback_base = base_url.replace("https://", "http://", 1)
  67.             fallback_meta = urllib.parse.urljoin(fallback_base, "ImageProperties.xml")
  68.             with urllib.request.urlopen(fallback_meta) as resp:
  69.                 xml_data = resp.read()
  70.             effective_base = fallback_base
  71.         else:
  72.             raise
  73.  
  74.     root = ET.fromstring(xml_data)
  75.     return (
  76.         int(root.attrib["WIDTH"]),
  77.         int(root.attrib["HEIGHT"]),
  78.         int(root.attrib["TILESIZE"]),
  79.         effective_base,
  80.     )
  81.  
  82.  
  83. def tile_url(base_url: str, x: int, y: int, z: int, tiles_x: int, offset: int) -> str:
  84.     tile_index = x + y * tiles_x + offset
  85.     group = tile_index // 256
  86.     return f"{base_url}TileGroup{group}/{z}-{x}-{y}.jpg"
  87.  
  88.  
  89. def output_name(base_url: str) -> str:
  90.     stem = base_url.rstrip("/").split("/")[-1]
  91.     return f"{stem or 'map'}.png"
  92.  
  93.  
  94. def main():
  95.     parser = argparse.ArgumentParser(
  96.         description="Download and stitch a map from mapy.mzk.cz / imageserver.mzk.cz."
  97.     )
  98.     parser.add_argument(
  99.         "url",
  100.         help="Map URL (e.g. https://mapy.mzk.cz/mzk03/001/053/827/2619316961/)",
  101.     )
  102.     parser.add_argument(
  103.         "-o",
  104.         "--output",
  105.         help="Output PNG filename (default: uses last path segment)",
  106.     )
  107.     args = parser.parse_args()
  108.  
  109.     base_url = normalize_base_url(args.url)
  110.     width, height, tile_size, base_url = fetch_image_properties(base_url)
  111.     tiers, offsets = zoomify_tiers(width, height, tile_size)
  112.     z = len(tiers) - 1
  113.     tiles_x, tiles_y = tiers[z]
  114.     offset = offsets[z]
  115.  
  116.     canvas = Image.new("RGB", (width, height))
  117.  
  118.     for y in range(tiles_y):
  119.         for x in range(tiles_x):
  120.             url = tile_url(base_url, x, y, z, tiles_x, offset)
  121.             try:
  122.                 with urllib.request.urlopen(url) as resp:
  123.                     data = resp.read()
  124.             except (ssl.SSLError, urllib.error.URLError):
  125.                 if base_url.startswith("https://"):
  126.                     base_url = base_url.replace("https://", "http://", 1)
  127.                     url = tile_url(base_url, x, y, z, tiles_x, offset)
  128.                     with urllib.request.urlopen(url) as resp:
  129.                         data = resp.read()
  130.                 else:
  131.                     raise
  132.             tile = Image.open(io.BytesIO(data))
  133.             canvas.paste(tile, (x * tile_size, y * tile_size))
  134.  
  135.     canvas = canvas.crop((0, 0, width, height))
  136.     filename = args.output or output_name(base_url)
  137.     canvas.save(filename)
  138.  
  139.  
  140. if __name__ == "__main__":
  141.     main()
  142.  
Add Comment
Please, Sign In to add comment