pax_global_header00006660000000000000000000000064137617153210014520gustar00rootroot0000000000000052 comment=bb7c3f83a5f37da888ab6a3e212acd88f04844c2 twms-0.07z+git20201202+bb7c3f8/000077500000000000000000000000001376171532100153545ustar00rootroot00000000000000twms-0.07z+git20201202+bb7c3f8/.hgtags000066400000000000000000000003531376171532100166330ustar00rootroot000000000000000a1cc6bba99284ea984710c18cf9bc116d791928 0.01q bfab1f075a7f33cfd760956b94f3de3fbcbefdf2 0.02w 962296dc4deb9b4821e21d0dd0e17e1fde2ef2d1 0.03e e40a1db64469baab230c1b37d0069bf15d2abf90 0.04r 413c35c82b67468f77e35663dc37040c681ccaa2 0.05t twms-0.07z+git20201202+bb7c3f8/COPYING000066400000000000000000000035211376171532100164100ustar00rootroot00000000000000The authors, Darafei Praliaskouski (Komzpa) and Andrew Shadura, explicitly disclaim copyright in all jurisdictions which recognise such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognise Public Domain property, this software is Copyright © 2009—2013 Darafei Praliaskouski Copyright © 2010—2013, 2016 Andrew Shadura and is released under the terms of the ISC License (see below). In jurisdictions which recognise Public Domain property, the user of this software may choose to accept it either as 1) Public Domain, 2) under the conditions of the ISC License (see below), or 3) under the terms of dual Public Domain/ISC License conditions described here, as they choose. The ISC License is compatible with both the copyleft and proprietary software, being as close to Public Domain as possible with the minor nuisance of being required to keep this copyright notice and license text in the source code. Note also that by accepting the Public Domain "license" you can re-license your copy using whatever license you like. The full text of the ISC License follows: Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. twms-0.07z+git20201202+bb7c3f8/README.md000066400000000000000000000012161376171532100166330ustar00rootroot00000000000000About ===== twms is a script that connects World of Tiles and World of WMS. The name ‘twms’ stands for twms web map server. The primary purpose of twms is to export your map tiles to the WMS-enabled applications. twms can export a set of raster tiles as a WMS service so GIS applications that support WMS protocol can access this tile set. Also, twms can act as a proxy and perform WMS requests to external services and serve the tile cache TODO ==== - Make fetchers work with proxy - Full reprojection support - Imagery realignment Conventions =========== - Inside tWMS, only EPSG:4326 latlon should be used for transmitting coordinates. twms-0.07z+git20201202+bb7c3f8/index.py000066400000000000000000000012571376171532100170420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. from twms import twms if __name__ != "__main__": try: from mod_python import apache, util import datetime except ImportError: pass def handler(req): """ A handler for mod_python. """ data = util.FieldStorage(req) data = dict((k.lower(), data[k]) for k in iter(data)) resp, ctype, content = twms.twms_main(data) req.content_type = ctype req.write(content) return resp twms-0.07z+git20201202+bb7c3f8/irs_nxt.jpg000066400000000000000000000000001376171532100175320ustar00rootroot00000000000000twms-0.07z+git20201202+bb7c3f8/setup.py000077500000000000000000000050721376171532100170750ustar00rootroot00000000000000#!/usr/bin/python3 import os import platform from glob import glob as abs_glob from setuptools import setup, find_packages __platform__ = platform.system() is_windows = __platform__ in ['Windows'] __name__ = "twms" __dir__ = os.path.dirname(__file__) def read(fname): return ( open(os.path.join(__dir__, fname), 'rb') .read() .decode('utf-8') ) def glob(fname): return [os.path.relpath(x, __dir__) for x in abs_glob(os.path.join(__dir__, fname))] def man_path(fname): category = fname.rsplit('.', 1)[1] return os.path.join('share', 'man', 'man' + category), [fname] def man_files(pattern): return list(map(man_path, glob(pattern))) def config_files(): if not is_windows: return [(os.path.join('/etc', __name__), [os.path.join('twms', 'twms.conf')])] else: return [] # monkey patch setuptools to use distutils owner/group functionality from setuptools.command import sdist sdist_org = sdist.sdist class sdist_new(sdist_org): def initialize_options(self): sdist_org.initialize_options(self) self.owner = self.group = 'root' sdist.sdist = sdist_new setup( name = __name__, version = "0.06y", author = 'Darafei Praliaskoiski', author_email = 'me@komzpa.net', url = 'https://github.com/komzpa/twms', description = 'tiny web map service', long_description = read('README.md'), license = 'Public Domain or ISC', packages = find_packages(), install_requires = ['Pillow', 'web.py'], extras_require = { 'proj': ['pyproj'], 'cairo': ['pycairo'], }, classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', 'License :: Public Domain', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Scientific/Engineering :: GIS', ], include_package_data = True, data_files = [ (os.path.join('share', 'doc', __name__), ['COPYING']), (os.path.join('share', 'doc', __name__), glob('*.md')), (os.path.join('share', __name__), glob('*.jpg')), (os.path.join('share', __name__, 'tools'), glob(os.path.join('tools', '*.py'))) ] + man_files('*.1') + config_files(), entry_points = { 'console_scripts': [ 'twms = twms.daemon:main' ] } ) twms-0.07z+git20201202+bb7c3f8/tools/000077500000000000000000000000001376171532100165145ustar00rootroot00000000000000twms-0.07z+git20201202+bb7c3f8/tools/compile_correction.py000066400000000000000000000031401376171532100227430ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- from __future__ import print_function import os import sys from lxml import etree tiles_cache = "/var/www/latlon/wms/cache/" layers = ["irs", "yhsat", "DGsat", "yasat", "SAT"] default_user = "Komzpa" user = default_user ts = "" corr = sys.stdout nodes = {} curway = [] for lay in layers: print(lay) src = lay + ".osm" if os.path.exists(src): corrfile = tiles_cache + lay + "/rectify.txt" corr = open(corrfile, "w") context = etree.iterparse(open(src)) for action, elem in context: items = dict(elem.items()) ts = items.get("timestamp", ts) # print items if elem.tag == "node": nodes[int(items["id"])] = (float(items["lon"]), float(items["lat"])) elif elem.tag == "nd": curway.append(nodes[int(items["ref"])]) elif elem.tag == "tag": if items["k"] == "user": user = items["v"] if items["k"] == "timestamp": ts = items["v"] elif elem.tag == "way": ts = items.get("timestamp", ts) print( "%s %s %s %s %s %s" % ( curway[0][0], curway[0][1], curway[1][0], curway[1][1], user, ts, ), file=corr, ) curway = [] user = default_user ts = "" twms-0.07z+git20201202+bb7c3f8/tools/decompile_correction.py000066400000000000000000000021531376171532100232570ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- from __future__ import print_function import sys tiles_cache = "/var/www/latlon/wms/cache/" layers = ["irs", "yhsat", "DGsat", "yasat"] out = sys.stdout for lay in layers: nid, wid = 1, 1 out = open(lay + ".osm", "w") corrfile = tiles_cache + lay + "/rectify.txt" corr = open(corrfile, "r") print('', file=out) for line in corr: d, c, b, a, user, ts = line.split() d, c, b, a = (float(d), float(c), float(b), float(a)) print('' % (nid, d, c), file=out) nid += 1 print('' % (nid, b, a), file=out) nid += 1 print('' % wid, file=out) print(' ' % (nid - 2), file=out) print(' ' % (nid - 1), file=out) print(' "' % ("user", user), file=out) print(' "' % ("timestamp", ts), file=out) print("", file=out) wid += 1 print("", file=out) twms-0.07z+git20201202+bb7c3f8/twms.1000066400000000000000000000027731376171532100164410ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH TWMS 1 "June 29, 2010" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME twms \- WMS (web map service) / TMS (tiled map service) server .SH SYNOPSIS .B twms .I [PORT] .br .SH DESCRIPTION This manual page documents briefly the .B twms command. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBtwms\fP is a WMS/TMS server capable of serving tiled images or proxying another WMS services .SH OPTIONS The only optional parameter \fBtwms\fR accepts is the port number on which it listens. Apart from that, it is fully configurable through its configuration file located at \fI/etc/twms/twms.conf\fR. .br .SH AUTHOR twms was written by Darafei Praliaskouski . .PP This manual page was written by Andrew Shadura , for the Debian project (and may be used by others). twms-0.07z+git20201202+bb7c3f8/twms/000077500000000000000000000000001376171532100163465ustar00rootroot00000000000000twms-0.07z+git20201202+bb7c3f8/twms/__init__.py000066400000000000000000000002221376171532100204530ustar00rootroot00000000000000# -*- coding: utf-8 -*- __all__ = [ "twms", "bbox", "canvas", "correctify", "drawing", "projections", "reproject", ] twms-0.07z+git20201202+bb7c3f8/twms/bbox.py000066400000000000000000000060151376171532100176540ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. import sys import projections def point_is_in(bbox, point): """ Check whether EPSG:4326 point is in bbox """ # bbox = normalize(bbox)[0] return ( point[0] >= bbox[0] and point[0] <= bbox[2] and point[1] >= bbox[1] and point[1] <= bbox[3] ) def bbox_is_in(bbox_outer, bbox_to_check, fully=True): """ Check whether EPSG:4326 bbox is inside outer """ bo = normalize(bbox_outer)[0] bc = normalize(bbox_to_check)[0] if fully: return (bo[0] <= bc[0] and bo[2] >= bc[2]) and ( bo[1] <= bc[1] and bo[3] >= bc[3] ) else: if bo[0] > bc[0]: bo, bc = bc, bo if bc[0] <= bo[2]: if bo[1] > bc[1]: bo, bc = bc, bo return bc[1] <= bo[3] return False return ( ((bo[0] <= bc[0] and bo[2] >= bc[0]) or (bo[0] <= bc[2] and bo[2] >= bc[2])) and ( (bo[1] <= bc[1] and bo[3] >= bc[1]) or (bo[1] <= bc[3] and bo[3] >= bc[3]) ) or ( (bc[0] <= bo[0] and bc[2] >= bo[0]) or (bc[0] <= bo[2] and bc[2] >= bo[2]) ) and ( (bc[1] <= bo[1] and bc[3] >= bo[1]) or (bc[1] <= bo[3] and bc[3] >= bo[3]) ) ) def add(b1, b2): """ Return bbox containing two bboxes. """ return (min(b1[0], b2[0]), min(b1[1], b2[1]), max(b1[2], b2[2]), max(b1[3], b2[3])) def expand_to_point(b1, p1): """ Expand bbox b1 to contain p1: [(x,y),(x,y)] """ for p in p1: b1 = add(b1, (p[0], p[1], p[0], p[1])) return b1 def normalize(bbox): """ Normalise EPSG:4326 bbox order. Returns normalized bbox, and whether it was flipped on horizontal axis. """ flip_h = False bbox = list(bbox) while bbox[0] < -180.0: bbox[0] += 360.0 bbox[2] += 360.0 if bbox[0] > bbox[2]: bbox = (bbox[0], bbox[1], bbox[2] + 360, bbox[3]) # bbox = (bbox[2],bbox[1],bbox[0],bbox[3]) if bbox[1] > bbox[3]: flip_h = True bbox = (bbox[0], bbox[3], bbox[2], bbox[1]) return bbox, flip_h def zoom_for_bbox(bbox, size, layer, min_zoom=1, max_zoom=18, max_size=(10000, 10000)): """ Calculate a best-fit zoom level """ h, w = size for i in range(min_zoom, max_zoom): cx1, cy1, cx2, cy2 = projections.tile_by_bbox(bbox, i, layer["proj"]) if w != 0: if (cx2 - cx1) * 256 >= w * 0.9: return i if h != 0: if (cy1 - cy2) * 256 >= h * 0.9: return i if (cy1 - cy2) * 256 >= max_size[0] / 2: return i if (cx2 - cx1) * 256 >= max_size[1] / 2: return i return max_zoom twms-0.07z+git20201202+bb7c3f8/twms/canvas.py000066400000000000000000000117201376171532100201740ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. ## ## PP - PrePare ## DL - DownLoading ## RD - ReaDy ## import projections try: from PIL import Image, ImageFilter except ImportError: import Image, ImageFilter import urllib from io import BytesIO import datetime import sys import threading def debug(st): sys.stderr.write(str(st) + "\n") class WmsCanvas: def __init__( self, wms_url=None, proj="EPSG:4326", zoom=4, tile_size=None, mode="RGBA", tile_mode="WMS", ): self.wms_url = wms_url self.zoom = zoom self.proj = proj self.mode = mode self.tile_mode = tile_mode self.tile_height = 256 self.tile_width = 256 if tile_size: self.tile_width, self.tile_height = tile_size self.tiles = {} def __setitem__(self, x, v): x, y = x tile_x = int(x / self.tile_height) x = x % self.tile_height tile_y = int(y / self.tile_width) y = y % self.tile_width try: self.tiles[(tile_x, tile_y)]["pix"][x, y] = v except KeyError: self.FetchTile(tile_x, tile_y) self.tiles[(tile_x, tile_y)]["pix"][x, y] = v def __getitem__(self, x): x, y = x tile_x = int(x / self.tile_height) x = x % self.tile_height tile_y = int(y / self.tile_width) y = y % self.tile_width try: return self.tiles[(tile_x, tile_y)]["pix"][x, y] except KeyError: self.FetchTile(tile_x, tile_y) return self.tiles[(tile_x, tile_y)]["pix"][x, y] def ConstructTileUrl(self, x, y): if self.tile_mode == "WMS": a, b, c, d = projections.from4326( projections.bbox_by_tile(self.zoom, x, y, self.proj), self.proj ) return self.wms_url + "width=%s&height=%s&srs=%s&bbox=%s,%s,%s,%s" % ( self.tile_width, self.tile_height, self.proj, a, b, c, d, ) else: return self.wms_url + "z=%s&x=%s&y=%s&width=%s&height=%s" % ( self.zoom - 1, x, y, self.tile_width, self.tile_height, ) def FetchTile(self, x, y): if (x, y) in self.tiles: if self.tiles[(x, y)]["status"] == "DL": self.tiles[(x, y)]["thread"].join() else: im = "" if self.wms_url: remote = self.ConstructTileUrl(x, y) debug(remote) ttz = datetime.datetime.now() contents = urllib.urlopen(remote).read() debug("Download took %s" % str(datetime.datetime.now() - ttz)) im = Image.open(BytesIO(contents)) if im.mode != self.mode: im = im.convert(self.mode) else: im = Image.new(self.mode, (self.tile_width, self.tile_height)) debug("blanking tile") self.tiles[(x, y)] = {} self.tiles[(x, y)]["im"] = im self.tiles[(x, y)]["pix"] = im.load() self.tiles[(x, y)]["status"] = "RD" def PreparePixel(self, x, y): tile_x = int(x / self.tile_height) x = x % self.tile_height tile_y = int(y / self.tile_width) y = y % self.tile_width if not (tile_x, tile_y): self.tiles[(tile_x, tile_y)] = {} self.tiles[(tile_x, tile_y)]["status"] = "PP" if not "pix" in self.tiles[(tile_x, tile_y)]: if self.tiles[(tile_x, tile_y)]["status"] == "PP": self.tiles[(tile_x, tile_y)]["status"] = "DL" self.tiles[(tile_x, tile_y)]["thread"] = threading.Thread( group=None, target=self.FetchTile, name=None, args=(self, tile_x, tile_y), kwargs={}, ) self.tiles[(tile_x, tile_y)]["thread"].start() return self.tiles[(tile_x, tile_y)]["status"] == "RD" def PixelAs4326(self, x, y): return projections.coords_by_tile( self.zoom, 1.0 * x / self.tile_width, 1.0 * y / self.tile_height, self.proj ) def PixelFrom4326(self, lon, lat): a, b = projections.tile_by_coords((lon, lat), self.zoom, self.proj) return a * self.tile_width, b * self.tile_height def MaxFilter(self, size=5): tiles = self.tiles.keys() for tile in tiles: self.tiles[tile]["im"] = self.tiles[tile]["im"].filter( ImageFilter.MedianFilter(size) ) self.tiles[tile]["pix"] = self.tiles[tile]["im"].load() twms-0.07z+git20201202+bb7c3f8/twms/capabilities.py000066400000000000000000000246421376171532100213610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. import config import projections def get(version, ref): content_type = "text/xml" if version == "1.0.0": req = ( """ ]> GetMap """ + config.wms_name + """ None """ + ref + """ none none """ + config.wms_name + """ """ ) pset = set(projections.projs.keys()) pset = pset.union(set(projections.proj_alias.keys())) for proj in pset: req += "%s" % proj req += """ """ lala = """ %s %s """ for i in config.layers.keys(): b = config.layers[i].get("bbox", config.default_bbox) req += lala % (i, config.layers[i]["name"], b[0], b[1], b[2], b[3]) req += """ """ else: content_type = "application/vnd.ogc.wms_xml" req = ( """ ]> twms """ + config.wms_name + """ None """ + config.contact_person["real_name"] + """ """ + config.contact_person["organization"] + """ """ + config.contact_person["mail"] + """ none none application/vnd.ogc.wms_xml image/png image/jpeg image/gif image/bmp application/vnd.ogc.se_inimage application/vnd.ogc.se_blank application/vnd.ogc.se_xml text/xml text/plain World Map""" ) pset = set(projections.projs.keys()) pset = pset.union(set(projections.proj_alias.keys())) for proj in pset: req += "%s" % proj req += """ """ lala = """ %s %s """ for i in config.layers.keys(): b = config.layers[i].get("bbox", config.default_bbox) req += lala % (i, config.layers[i]["name"], b[0], b[1], b[2], b[3]) req += """ """ return content_type, req twms-0.07z+git20201202+bb7c3f8/twms/correctify.py000066400000000000000000000076551376171532100211060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. import projections import os import config distance = lambda z, x, y, g: ((z - y) ** 2 + (x - g) ** 2) ** (0.5) def has_corrections(layer): corrfile = config.tiles_cache + layer.get("prefix", "") + "/rectify.txt" return os.path.exists(corrfile) def corr_wkt(layer): corrfile = config.tiles_cache + layer.get("prefix", "") + "/rectify.txt" corr = open(corrfile, "r") wkt = "" for line in corr: d, c, b, a, user, ts = line.split() d, c, b, a = (float(d), float(c), float(b), float(a)) wkt += "POINT(%s %s),LINESTRING(%s %s,%s %s)," % (d, c, d, c, b, a) return wkt[:-1] def rectify(layer, point): corrfile = config.tiles_cache + layer.get("prefix", "") + "/rectify.txt" srs = layer["proj"] if not os.path.exists(corrfile): return point corr = open(corrfile, "r") lons, lats = point loni, lati, lona, lata = projections.projs[projections.proj_alias.get(srs, srs)][ "bounds" ] if (lons is loni and lats is lati) or (lons is lona and lats is lata): return point # print(pickle.dumps(coefs[layer]), file=sys.stderr) # sys.stderr.flush() lonaz, loniz, lataz, latiz = lona, loni, lata, lati maxdist = 1.80 for line in corr: d, c, b, a, user, ts = line.split() d, c, b, a = (float(d), float(c), float(b), float(a)) # for d,c,b,a in coefs[layer]: # print(a,b, distance(lons, lats, b, a), file=sys.stderr) if distance(b, a, lons, lats) < maxdist: if a > lats: if distance(a, b, lats, lons) <= distance(lata, lona, lats, lons): lata = a lataz = c if a < lats: if distance(a, b, lats, lons) <= distance(lati, loni, lats, lons): lati = a latiz = c if b > lons: if distance(a, b, lats, lons) <= distance(lata, lona, lats, lons): lona = b lonaz = d if b < lons: if distance(a, b, lats, lons) <= distance(lati, loni, lats, lons): loni = b loniz = d # print(loni, lati, lona, lata, distance(loni, lati, lona, lata), file=sys.stderr) # print("clat:", (lata-lati)/(lataz-latiz), (lona-loni)/(lonaz-loniz), file=sys.stderr) # sys.stderr.flush() lons, lats = projections.from4326(point, srs) lona, lata = projections.from4326((lona, lata), srs) loni, lati = projections.from4326((loni, lati), srs) lonaz, lataz = projections.from4326((lonaz, lataz), srs) loniz, latiz = projections.from4326((loniz, latiz), srs) latn = (lats - lati) / (lata - lati) latn = (latn * (lataz - latiz)) + latiz lonn = (lons - loni) / (lona - loni) lonn = (lonn * (lonaz - loniz)) + loniz return projections.to4326((lonn, latn), srs) # print(rectify("yasat", (27.679068, 53.885122), "")) def r_bbox(layer, bbox): corrfile = config.tiles_cache + layer.get("prefix", "") + "/rectify.txt" srs = layer["proj"] if not os.path.exists(corrfile): return bbox a, b, c, d = projections.from4326(bbox, srs) cx, cy = (a + c) / 2, (b + d) / 2 cx1, cy1 = projections.from4326( rectify(layer, projections.to4326((cx, cy), srs)), srs ) a1, b1 = projections.from4326(rectify(layer, projections.to4326((a, b), srs)), srs) c1, d1 = projections.from4326(rectify(layer, projections.to4326((c, d), srs)), srs) dx, dy = ( ((cx1 - cx) + (a1 - a) + (c1 - c)) / 3, ((cy1 - cy) + (b1 - b) + (d1 - d)) / 3, ) # print(dx,dy, file=sys.stderr) # sys.stderr.flush() return projections.to4326((a + dx, b + dy, c + dx, d + dy), srs) twms-0.07z+git20201202+bb7c3f8/twms/daemon.py000077500000000000000000000043361376171532100201740ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. from __future__ import print_function import web import sys from twms import * import sys, socket try: import psyco psyco.full() except ImportError: pass OK = 200 ERROR = 500 def handler(data): """ A handler for web.py. """ resp, ctype, content = twms_main(data) web.header("Content-Type", ctype) return content urls = ( "/(.*)/([0-9]+)/([0-9]+)/([0-9]+)(\.[a-zA-Z]+)?(.*)", "tilehandler", "/(.*)", "mainhandler", ) class tilehandler: def GET(self, layers, z, x, y, format, rest): if format is None: format = "jpeg" else: format = format.lower() data = { "request": "GetTile", "layers": layers, "format": format.strip("."), "z": z, "x": x, "y": y, } return handler(data) class mainhandler: def GET(self, crap): data = web.input() data = dict((k.lower(), data[k]) for k in iter(data)) if "ref" not in data: if web.ctx.env["HTTP_HOST"]: data["ref"] = ( web.ctx.env["wsgi.url_scheme"] + "://" + web.ctx.env["HTTP_HOST"] + "/" ) return handler(data) def main(): try: if sys.argv[1] == "josm": # josm mode import cgi url, params = sys.argv[2].split("/?", 1) data = cgi.parse_qs(params) for t in data.keys(): data[t] = data[t][0] resp, ctype, content = twms_main(data) print(content) exit() except IndexError: pass try: app = web.application(urls, globals()) app.run() # standalone run except socket.error: print("Can't open socket. Abort.", file=sys.stderr) sys.exit(1) application = web.application(urls, globals()).wsgifunc() # mod_wsgi if __name__ == "__main__": main() twms-0.07z+git20201202+bb7c3f8/twms/drawing.py000066400000000000000000000115551376171532100203620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. try: from PIL import Image, ImageDraw, ImageColor, ImageFont except ImportError: import Image, ImageDraw, ImageColor, ImageFont import urllib import os, sys import array import projections import config from gpxparse import GPXParser import math HAVE_CAIRO = True try: import cairo import pango import pangocairo except ImportError: HAVE_CAIRO = False def wkt(wkt, img, bbox, srs, color, blend=0.5): """ Simple WKT renderer """ wkt = wkt.replace("((", "(") for obj in wkt.split("),"): canvas = img.copy() name, coords = obj.split("(") coords = coords.replace(")", "") text = "" if name == "TEXT": text, coords = coords.split(",", 1) coords = coords.split(",") coords = [[float(t) for t in x.split(" ")] for x in coords] canvas = render_vector(name, canvas, bbox, coords, srs, color, text=text) img = Image.blend(img, canvas, blend) return img def gpx(track, img, bbox, srs, color, blend=0.5): """ Simple GPX renderer """ for i in track.tracks.keys(): coords = track.getTrack(i) canvas = img.copy() canvas = render_vector("LINESTRING", canvas, bbox, coords, srs, color) img = Image.blend(img, canvas, blend) return img def render_vector( geometry, img, bbox, coords, srs, color=None, renderer=None, linestring_width=None, text=None, cairo_font=None, pil_font=None, ): """ Renders a vector geometry on image. """ if not color: color = config.geometry_color[geometry] if not renderer: renderer = config.default_vector_renderer if not linestring_width: if "linestring_width" in dir(config): linestring_width = config.linestring_width else: linestring_width = 3 if not cairo_font: if "cairo_font" in dir(config): cairo_font = config.cairo_font if not pil_font: if "pil_font" in dir(config): pil_font = config.pil_font bbox = projections.from4326(bbox, srs) lo1, la1, lo2, la2 = bbox coords = projections.from4326(coords, srs) W, H = img.size prevcoord = False coords = [ ( (coord[0] - lo1) * (W - 1) / abs(lo2 - lo1), (la2 - coord[1]) * (H - 1) / (la2 - la1), ) for coord in coords ] if renderer == "cairo" and HAVE_CAIRO: "rendering as cairo" imgd = img.tostring() a = array.array("B", imgd) surface = cairo.ImageSurface.create_for_data( a, cairo.FORMAT_ARGB32, W, H, W * 4 ) cr = cairo.Context(surface) cr.move_to(*coords[0]) color = ImageColor.getrgb(color) cr.set_source_rgba(color[2] / 255.0, color[1] / 255.0, color[0] / 255.0, 1) if geometry == "LINESTRING" or geometry == "POLYGON": for k in coords: cr.line_to(*k) if geometry == "LINESTRING": if linestring_width is not None: cr.set_line_width(linestring_width) cr.stroke() elif geometry == "POLYGON": cr.fill() elif geometry == "POINT": cr.arc(coords[0][0], coords[0][1], 6, 0, 2 * math.pi) cr.fill() elif geometry == "TEXT" and text and cairo_font: pcr = pangocairo.CairoContext(cr) pcr.set_antialias(cairo.ANTIALIAS_SUBPIXEL) layout = pcr.create_layout() font = pango.FontDescription(cairo_font) layout.set_font_description(font) layout.set_text(text) pcr.update_layout(layout) pcr.show_layout(layout) img = Image.frombuffer("RGBA", (W, H), surface.get_data(), "raw", "RGBA", 0, 1) else: "falling back to PIL" coord = [ (int(coord[0]), int(coord[1])) for coord in coords ] # PIL dislikes subpixels draw = ImageDraw.Draw(img) if geometry == "LINESTRING": draw.line(coords, fill=color, width=linestring_width) elif geometry == "POINT": draw.ellipse( ( coords[0][0] - 3, coords[0][1] - 3, coords[0][0] + 3, coords[0][1] + 3, ), fill=color, outline=color, ) elif geometry == "POLYGON": draw.polygon(coords, fill=color, outline=color) elif geometry == "TEXT" and text and pil_font: font = ImageFont.truetype(pil_font[0], pil_font[1]) draw.text(coords[0], text, color, font=font) return img twms-0.07z+git20201202+bb7c3f8/twms/fetchers.py000066400000000000000000000130501376171532100205220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. from urllib.request import urlopen import filecmp import time import os import math import sys from io import BytesIO try: from PIL import Image except ImportError: import Image import time import config import projections import threading fetching_now = {} thread_responses = {} zhash_lock = {} def fetch(z, x, y, this_layer): zhash = repr((z, x, y, this_layer)) try: zhash_lock[zhash] += 1 except KeyError: zhash_lock[zhash] = 1 if zhash not in fetching_now: atomthread = threading.Thread( None, threadwrapper, None, (z, x, y, this_layer, zhash) ) atomthread.start() fetching_now[zhash] = atomthread if fetching_now[zhash].is_alive(): fetching_now[zhash].join() resp = thread_responses[zhash] zhash_lock[zhash] -= 1 if not zhash_lock[zhash]: del thread_responses[zhash] del fetching_now[zhash] del zhash_lock[zhash] return resp def threadwrapper(z, x, y, this_layer, zhash): try: thread_responses[zhash] = this_layer["fetch"](z, x, y, this_layer) except OSError: for i in range(20): time.sleep(0.1) try: thread_responses[zhash] = this_layer["fetch"](z, x, y, this_layer) return except OSError: continue thread_responses[zhash] = None def WMS(z, x, y, this_layer): if "max_zoom" in this_layer: if z >= this_layer["max_zoom"]: return None wms = this_layer["remote_url"] req_proj = this_layer.get("wms_proj", this_layer["proj"]) width = 384 # using larger source size to rescale better in python height = 384 local = ( config.tiles_cache + this_layer["prefix"] + "/z%s/%s/x%s/%s/y%s." % (z, x / 1024, x, y / 1024, y) ) tile_bbox = "bbox=%s,%s,%s,%s" % tuple( projections.from4326(projections.bbox_by_tile(z, x, y, req_proj), req_proj) ) wms += tile_bbox + "&width=%s&height=%s&srs=%s" % (width, height, req_proj) if this_layer.get("cached", True): if not os.path.exists("/".join(local.split("/")[:-1])): os.makedirs("/".join(local.split("/")[:-1])) try: os.mkdir(local + "lock") except OSError: for i in range(20): time.sleep(0.1) try: if not os.path.exists(local + "lock"): im = Image.open(local + this_layer["ext"]) return im except (IOError, OSError): return None im = Image.open(BytesIO(urlopen(wms).read())) if width != 256 and height != 256: im = im.resize((256, 256), Image.ANTIALIAS) im = im.convert("RGBA") if this_layer.get("cached", True): ic = Image.new( "RGBA", (256, 256), this_layer.get("empty_color", config.default_background) ) if im.histogram() == ic.histogram(): tne = open(local + "tne", "wb") when = time.localtime() tne.write( "%02d.%02d.%04d %02d:%02d:%02d" % (when[2], when[1], when[0], when[3], when[4], when[5]) ) tne.close() return False im.save(local + this_layer["ext"]) os.rmdir(local + "lock") return im def Tile(z, x, y, this_layer): global OSError, IOError d_tuple = z, x, y if "max_zoom" in this_layer: if z >= this_layer["max_zoom"]: return None if "transform_tile_number" in this_layer: d_tuple = this_layer["transform_tile_number"](z, x, y) remote = this_layer["remote_url"] % d_tuple if this_layer.get("cached", True): local = ( config.tiles_cache + this_layer["prefix"] + "/z%s/%s/x%s/%s/y%s." % (z, x / 1024, x, y / 1024, y) ) if not os.path.exists("/".join(local.split("/")[:-1])): os.makedirs("/".join(local.split("/")[:-1])) try: os.mkdir(local + "lock") except OSError: for i in range(20): time.sleep(0.1) try: if not os.path.exists(local + "lock"): im = Image.open(local + this_layer["ext"]) return im except (IOError, OSError): return None try: contents = urlopen(remote).read() im = Image.open(BytesIO(contents)) except IOError: if this_layer.get("cached", True): os.rmdir(local + "lock") return False if this_layer.get("cached", True): os.rmdir(local + "lock") open(local + this_layer["ext"], "wb").write(contents) if "dead_tile" in this_layer: try: dt = open(this_layer["dead_tile"], "rb").read() if contents == dt: if this_layer.get("cached", True): tne = open(local + "tne", "wb") when = time.localtime() tne.write( "%02d.%02d.%04d %02d:%02d:%02d" % (when[2], when[1], when[0], when[3], when[4], when[5]) ) tne.close() os.remove(local + this_layer["ext"]) return False except IOError: pass return im twms-0.07z+git20201202+bb7c3f8/twms/filter.py000066400000000000000000000072011376171532100202050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. try: from PIL import Image, ImageFilter, ImageEnhance, ImageOps except ImportError: import Image, ImageFilter, ImageEnhance, ImageOps try: import numpy NUMPY_AVAILABLE = True except ImportError: NUMPY_AVAILABLE = False import datetime from twms import getimg try: import config except: pass def raster(result_img, filt, bbox=(-999, -999, 999, 9999), srs="EPSG:3857"): """ Applies various filters to image. """ for ff in filt: if ff.split(":") == [ff]: if ff == "bw": result_img = result_img.convert("L") result_img = result_img.convert("RGBA") if ff == "contour": result_img = result_img.filter(ImageFilter.CONTOUR) if ff == "median": result_img = result_img.filter(ImageFilter.MedianFilter(5)) if ff == "blur": result_img = result_img.filter(ImageFilter.BLUR) if ff == "edge": result_img = result_img.filter(ImageFilter.EDGE_ENHANCE) if ff == "equalize": result_img = result_img.convert("RGB") result_img = ImageOps.equalize(result_img) result_img = result_img.convert("RGBA") if ff == "autocontrast": result_img = result_img.convert("RGB") result_img = ImageOps.autocontrast(result_img) result_img = result_img.convert("RGBA") if ff == "swaprb": ii = result_img.split() result_img = Image.merge("RGBA", (ii[2], ii[1], ii[0], ii[3])) else: ff, tts = ff.split(":") try: tt = float(tts) except ValueError: tt = 1 pass if ff == "brightness": enhancer = ImageEnhance.Brightness(result_img) result_img = enhancer.enhance(tt) if ff == "contrast": enhancer = ImageEnhance.Contrast(result_img) result_img = enhancer.enhance(tt) if ff == "sharpness": enhancer = ImageEnhance.Sharpness(result_img) result_img = enhancer.enhance(tt) if ff == "autocontrast": result_img = result_img.convert("RGB") result_img = ImageOps.autocontrast(result_img, tt) result_img = result_img.convert("RGBA") if ff == "fusion" and NUMPY_AVAILABLE: pix = numpy.array(result_img, dtype=int) a, b = result_img.size pan_img = getimg( bbox, srs, [b, a], config.layers[tts], datetime.datetime.now(), [] ) pan_img = pan_img.convert("L") print(pix.dtype) print(pix[..., 1].shape) pan = numpy.array(pan_img) pix[..., 0] = ( pix[..., 0] * pan / (pix[..., 0] + pix[..., 1] + pix[..., 2]) ) pix[..., 1] = ( pix[..., 1] * pan / (pix[..., 0] + pix[..., 1] + pix[..., 2]) ) pix[..., 2] = ( pix[..., 2] * pan / (pix[..., 0] + pix[..., 1] + pix[..., 2]) ) print(pix.shape) result_img = Image.fromarray(numpy.uint8(pix)) result_img = result_img.convert("RGBA") return result_img twms-0.07z+git20201202+bb7c3f8/twms/gpxparse.py000066400000000000000000000047071376171532100205610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. import sys, string, bz2, gzip, os from xml.dom import minidom, Node class GPXParser: def __init__(self, filename): self.tracks = {} self.pointnum = 0 self.trknum = 0 self.bbox = (999, 999, -999, -999) try: file = open(filename) signature = file.read(2) file.close() file = { "BZ": lambda f: bz2.BZ2File(f), "\x1f\x8b": lambda f: gzip.GzipFile(f), " maxlat: maxlat = lat if lat < minlat: minlat = lat if lon > maxlon: maxlon = lon if lon < minlon: minlon = lon # ele = float(trkpt.getElementsByTagName('ele')[0].firstChild.data) rfc3339 = trkpt.getElementsByTagName("time")[0].firstChild.data self.pointnum += 1 self.tracks[name][self.pointnum] = {"lat": lat, "lon": lon} self.bbox = (minlon, minlat, maxlon, maxlat) def getTrack(self, name): times = self.tracks[name].keys() times.sort() points = [self.tracks[name][time] for time in times] return [(point["lon"], point["lat"]) for point in points] twms-0.07z+git20201202+bb7c3f8/twms/overview.py000066400000000000000000000032411376171532100205660ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. from config import * import projections def html(ref): """ Gives overall information about twms server and its layers in HTML format. """ resp = "" resp += "" resp += wms_name resp += "

" resp += wms_name resp += "

" for i in layers: bbox = layers[i].get( "data_bounding_box", projections.projs[layers[i]["proj"]]["bounds"] ) resp += '" resp += "

' ) resp += layers[i]["name"] resp += ( "

Bounding box: " + str(bbox) + ' (show on OSM' % bbox + ")
" ) resp += "Projection: " + layers[i]["proj"] + "
" resp += "WMS half-link: " + ref + "?layers=" + i + "&
" resp += ( "Tiles URL: " + ref + "" + i + "/!/!/!." + layers[i].get("ext", "jpg") + "
" ) resp += "
" return resp twms-0.07z+git20201202+bb7c3f8/twms/projections.py000066400000000000000000000265601376171532100212700ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. import math try: import pyproj except ImportError: class pyproj: class Proj: def __init__(self, pstring): self.pstring = pstring def transform(self, pr1, pr2, c1, c2): if pr1.pstring == pr2.pstring: return c1, c2 else: raise NotImplementedError( "Pyproj is not installed - can't convert between projectios. Install pyproj please." ) projs = { "EPSG:4326": { "proj": pyproj.Proj("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "NASA:4326": { "proj": pyproj.Proj("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"), "bounds": (-180.0, -166.0, 332.0, 346.0), }, "EPSG:3395": { "proj": pyproj.Proj( "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -85.0840591556, 180.0, 85.0840590501), }, "EPSG:3857": { "proj": pyproj.Proj( "+proj=merc +lon_0=0 +lat_ts=0 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +units=m +no_defs" ), "bounds": (-180.0, -85.0511287798, 180.0, 85.0511287798), }, "EPSG:32635": { "proj": pyproj.Proj( "+proj=utm +zone=35 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "EPSG:32636": { "proj": pyproj.Proj( "+proj=utm +zone=36 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "EPSG:32637": { "proj": pyproj.Proj( "+proj=utm +zone=37 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "EPSG:32638": { "proj": pyproj.Proj( "+proj=utm +zone=38 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "EPSG:32639": { "proj": pyproj.Proj( "+proj=utm +zone=39 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "EPSG:32640": { "proj": pyproj.Proj( "+proj=utm +zone=40 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "EPSG:32641": { "proj": pyproj.Proj( "+proj=utm +zone=41 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, "EPSG:32642": { "proj": pyproj.Proj( "+proj=utm +zone=42 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" ), "bounds": (-180.0, -90.0, 180.0, 90.0), }, } proj_alias = {"EPSG:900913": "EPSG:3857", "EPSG:3785": "EPSG:3857"} def _c4326t3857(t1, t2, lon, lat): """ Pure python 4326 -> 3857 transform. About 8x faster than pyproj. """ lat_rad = math.radians(lat) xtile = lon * 111319.49079327358 ytile = ( math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi * 20037508.342789244 ) return (xtile, ytile) def _c3857t4326(t1, t2, lon, lat): """ Pure python 3857 -> 4326 transform. About 12x faster than pyproj. """ xtile = lon / 111319.49079327358 ytile = math.degrees(math.asin(math.tanh(lat / 20037508.342789244 * math.pi))) return (xtile, ytile) def _c4326t3395(t1, t2, lon, lat): """ Pure python 4326 -> 3395 transform. About 8x faster than pyproj. """ E = 0.0818191908426 A = 20037508.342789 F = 53.5865938 tmp = math.tan(0.78539816339744830962 + math.radians(lat) / 2.0) pow_tmp = math.pow( math.tan( 0.78539816339744830962 + math.asin(E * math.sin(math.radians(lat))) / 2.0 ), E, ) x = lon * 111319.49079327358 y = 6378137.0 * math.log(tmp / pow_tmp) return (x, y) def _c3395t4326(t1, t2, lon, lat): """ Pure python 4326 -> 3395 transform. About 3x faster than pyproj. """ r_major = 6378137.000 temp = 6356752.3142 / 6378137.000 es = 1.0 - (temp * temp) eccent = math.sqrt(es) ts = math.exp(-lat / r_major) HALFPI = 1.5707963267948966 eccnth = 0.5 * eccent Phi = HALFPI - 2.0 * math.atan(ts) N_ITER = 15 TOL = 1e-7 i = N_ITER dphi = 0.1 while (abs(dphi) > TOL) and (i > 0): i -= 1 con = eccent * math.sin(Phi) dphi = ( HALFPI - 2.0 * math.atan(ts * math.pow((1.0 - con) / (1.0 + con), eccnth)) - Phi ) Phi += dphi x = lon / 111319.49079327358 return (x, math.degrees(Phi)) pure_python_transformers = { ("EPSG:4326", "EPSG:3857"): _c4326t3857, ("EPSG:3857", "EPSG:4326"): _c3857t4326, ("EPSG:4326", "EPSG:3395"): _c4326t3395, ("EPSG:3395", "EPSG:4326"): _c3395t4326, } def tile_by_bbox(bbox, zoom, srs="EPSG:3857"): """ Converts bbox from 4326 format to tile numbers of given zoom level, with correct wraping around 180th meridian """ a1, a2 = tile_by_coords((bbox[0], bbox[1]), zoom, srs) b1, b2 = tile_by_coords((bbox[2], bbox[3]), zoom, srs) if b1 < a1: b1 += 2 ** (zoom - 1) return a1, a2, b1, b2 def bbox_by_tile(z, x, y, srs="EPSG:3857"): """ Tile numbers of given zoom level to EPSG:4326 bbox of srs-projected tile """ a1, a2 = coords_by_tile(z, x, y, srs) b1, b2 = coords_by_tile(z, x + 1, y + 1, srs) return a1, b2, b1, a2 def coords_by_tile(z, x, y, srs="EPSG:3857"): """ Converts (z,x,y) to coordinates of corner of srs-projected tile """ z -= 1 normalized_tile = (x / (2.0 ** z), 1.0 - (y / (2.0 ** z))) projected_bounds = from4326(projs[proj_alias.get(srs, srs)]["bounds"], srs) maxp = [ projected_bounds[2] - projected_bounds[0], projected_bounds[3] - projected_bounds[1], ] projected_coords = [ (normalized_tile[0] * maxp[0]) + projected_bounds[0], (normalized_tile[1] * maxp[1]) + projected_bounds[1], ] return to4326(projected_coords, srs) def tile_by_coords(xxx_todo_changeme, zoom, srs="EPSG:3857"): """ Converts EPSG:4326 latitude and longitude to tile number of srs-projected tile pyramid. lat, lon - EPSG:4326 coordinates of a point zoom - zoomlevel of tile number srs - text string, specifying projection of tile pyramid """ (lon, lat) = xxx_todo_changeme zoom -= 1 projected_bounds = from4326(projs[proj_alias.get(srs, srs)]["bounds"], srs) point = from4326((lon, lat), srs) point = [point[0] - projected_bounds[0], point[1] - projected_bounds[1]] # shifting (0,0) maxp = [ projected_bounds[2] - projected_bounds[0], projected_bounds[3] - projected_bounds[1], ] point = [1.0 * point[0] / maxp[0], 1.0 * point[1] / maxp[1]] # normalizing return point[0] * (2 ** zoom), (1 - point[1]) * (2 ** zoom) def to4326(line, srs="EPSG:3857"): """ Wrapper around transform call for convenience. Transforms line from srs to EPSG:4326 line - a list of [lat0,lon0,lat1,lon1,...] or [(lat0,lon0),(lat1,lon1),...] srs - text string, specifying projection """ return transform(line, srs, "EPSG:4326") def from4326(line, srs="EPSG:3857"): """ Wrapper around transform call for convenience. Transforms line from EPSG:4326 to srs line - a list of [lat0,lon0,lat1,lon1,...] or [(lat0,lon0),(lat1,lon1),...] srs - text string, specifying projection """ return transform(line, "EPSG:4326", srs) def transform(line, srs1, srs2): """ Converts a bunch of coordinates from srs1 to srs2. line - a list of [lat0,lon0,lat1,lon1,...] or [(lat0,lon0),(lat1,lon1),...] srs[1,2] - text string, specifying projection (srs1 - from, srs2 - to) """ srs1 = proj_alias.get(srs1, srs1) srs2 = proj_alias.get(srs2, srs2) if srs1 == srs2: return line if (srs1, srs2) in pure_python_transformers: func = pure_python_transformers[(srs1, srs2)] # print("pure") else: func = pyproj.transform line = list(line) serial = False if (not isinstance(line[0], tuple)) and (not isinstance(line[0], list)): serial = True l1 = [] while line: a = line.pop(0) b = line.pop(0) l1.append([a, b]) line = l1 ans = [] pr1 = projs[srs1]["proj"] pr2 = projs[srs2]["proj"] for point in line: p = func(pr1, pr2, point[0], point[1]) if serial: ans.append(p[0]) ans.append(p[1]) else: ans.append(p) return ans if __name__ == "__main__": import debug print(_c4326t3857(1, 2, 27.6, 53.2)) print(from4326((27.6, 53.2), "EPSG:3857")) a = _c4326t3857(1, 2, 27.6, 53.2) print(to4326(a, "EPSG:3857")) print(_c3857t4326(1, 2, a[0], a[1])) print("3395:") print(_c4326t3395(1, 2, 27.6, 53.2)) print(from4326((27.6, 53.2), "EPSG:3395")) a = _c4326t3395(1, 2, 27.6, 53.2) print(to4326(a, "EPSG:3395")) print(_c3395t4326(1, 2, a[0], a[1])) a = debug.Timer("Pure python 4326<3857") for i in xrange(0, 100000): t = _c3857t4326(1, 2, 3072417.9458943508, 7020078.5326420991) a.stop() a = debug.Timer("TWMS wrapped 4326<3857") for i in xrange(0, 100000): t = to4326((3072417.9458943508, 7020078.5326420991), "EPSG:3857") a.stop() a = debug.Timer("Pyproj unwrapped 4326<3857") pr1 = projs["EPSG:3857"]["proj"] pr2 = projs["EPSG:4326"]["proj"] for i in xrange(0, 100000): t = pyproj.transform(pr1, pr2, 3072417.9458943508, 7020078.5326420991) a.stop() a = debug.Timer("Pure python 4326<3395") for i in xrange(0, 100000): t = _c3395t4326(1, 2, 3072417.9458943508, 7020078.5326420991) a.stop() a = debug.Timer("TWMS wrapped 4326<3395") for i in xrange(0, 100000): t = to4326((3072417.9458943508, 7020078.5326420991), "EPSG:3395") a.stop() a = debug.Timer("Pyproj unwrapped 4326<3395") pr1 = projs["EPSG:3395"]["proj"] pr2 = projs["EPSG:4326"]["proj"] for i in xrange(0, 100000): t = pyproj.transform(pr1, pr2, 3072417.9458943508, 7020078.5326420991) a.stop() a = debug.Timer("Pure python 4326>3857") for i in xrange(0, 100000): t = _c4326t3857(1, 2, 27.6, 53.2) a.stop() a = debug.Timer("TWMS wrapped 4326>3857") for i in xrange(0, 100000): t = from4326((27.6, 53.2), "EPSG:3857") a.stop() a = debug.Timer("Pyproj unwrapped 4326>3857") pr2 = projs["EPSG:3857"]["proj"] pr1 = projs["EPSG:4326"]["proj"] for i in xrange(0, 100000): t = pyproj.transform(pr1, pr2, 27.6, 53.2) a.stop() a = debug.Timer("Pure python 4326>3395") for i in xrange(0, 100000): t = _c4326t3395(1, 2, 27.6, 53.2) a.stop() a = debug.Timer("TWMS wrapped 4326>3395") for i in xrange(0, 100000): t = from4326((27.6, 53.2), "EPSG:3395") a.stop() a = debug.Timer("Pyproj unwrapped 4326>3395") pr2 = projs["EPSG:3395"]["proj"] pr1 = projs["EPSG:4326"]["proj"] for i in xrange(0, 100000): t = pyproj.transform(pr1, pr2, 27.6, 53.2) a.stop() pass twms-0.07z+git20201202+bb7c3f8/twms/reproject.py000066400000000000000000000022521376171532100207160ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. try: from PIL import Image except ImportError: import Image import projections import sys def reproject(image, bbox, srs_from, srs_to): out = image.copy() op = out.load() il = image.load() bbox_from = projections.from4326(bbox, srs_from) bbox_to = projections.from4326(bbox, srs_to) width, height = image.size for x in range(0, width): for y in range(0, height): dx = ((1.0 * x / width) * (bbox_to[2] - bbox_to[0])) + bbox_to[0] dy = ((1.0 * y / height) * (bbox_to[3] - bbox_to[1])) + bbox_to[1] # print(x, dx, y, dy, file=sys.stderr) dx, dy = tuple(projections.transform([dx, dy], srs_to, srs_from)) dx = width * (dx - bbox_from[0]) / (bbox_from[2] - bbox_from[0]) dy = height * (dy - bbox_from[1]) / (bbox_from[3] - bbox_from[1]) op[x, y] = il[int(dx), int(dy)] # sys.stderr.flush() return out twms-0.07z+git20201202+bb7c3f8/twms/sketch.py000066400000000000000000000035131376171532100202030ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. from bbox import * string = "abcdefghijklmnopqrstuvwxyz012345ABCDEFGHIJKLMNOPQRSTUVWXYZ6789{}" def decode(bbox, sketch): version, sketch = sketch.split(";", 1) def encode_point(bbox, point, length, length_out=None): if not length_out: length_out = length code = "." if not point_is_in(bbox, point): bbox = (-180, -90, 180, 90) code += "@" length = length_out - 1 lon, lat = point lon = (lon - bbox[0]) / (bbox[2] - bbox[0]) # normalizing points to bbox lat = (lat - bbox[1]) / (bbox[3] - bbox[1]) lats, lons = [], [] for i in range(0, length): latt = int(lat * 8) lont = int(lon * 8) lat = lat * 8 - int(lat * 8) lon = lon * 8 - int(lon * 8) code += string[lont * 8 + latt] return code def decode_point(bbox, code): lat, lon = (0, 0) if code[0] == ".": code = code[1:] if code[0] == "@": code = code[1:] bbox = (-180, -90, 180, 90) c = "" code = " " + code # reverse for a in range(0, len(code) - 1): c += code[-1] code = code[:-1] code = c for t in code: z = string.find(t) lont = int(z / 8.0) latt = (z / 8.0 - int(z / 8.0)) * 8 lat += latt lat /= 8.0 lon += lont lon /= 8.0 lat = lat * (bbox[3] - bbox[1]) + bbox[1] lon = lon * (bbox[2] - bbox[0]) + bbox[0] return lon, lat # Window.alert(code_point((0,0,0,0), 53.11, 27.3434)) # print(decode_point((0,0,0,0), ".@aaa")) twms-0.07z+git20201202+bb7c3f8/twms/twms.conf000066400000000000000000000137121376171532100202130ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This file is part of tWMS. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. import fetchers debug = True max_ram_cached_tiles = 1024 tiles_cache = "/var/cache/twms/tiles/" # where to put cache install_path = "/usr/share/twms/" # where to look for broken tiles and other stuff gpx_cache = "/var/cache/twms/traces/" # where to store cached OSM GPX files deadline = 45 # number of seconds that are given to make up image default_max_zoom = 18 # can be overridden per layer geometry_color = { # default color for overlayed vectors "LINESTRING": "#ff0000", "POLYGON": "#0000ff", "POINT": "#00ff00", "TEXT": "#000000", } linestring_width = 3 cairo_font = "Sans 25" pil_font = ("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 25) default_layers = "" # layer(s) to show when no layers given explicitly. if False, overview is shown. max_height = 4095 # maximal image proportions max_width = 4095 output_quality = 75 # output image quality (matters for JPEG) output_progressive = True # should image be progressive? (matters for JPEG) output_optimize = False # should image be optimized? (matters for PNG) default_background = "#ffffff" # default background for empty space default_vector_renderer = "cairo" # "PIL" or "cairo" ## stuff used for GetCapabilities service_url = "http://localhost:8080/" # URL service installed at wms_name = "twms based web map service" default_bbox = (-180.0,-85.0511287798,180.0,85.0511287798) # spherical mercator maximum. default_format = "image/jpeg" contact_person = { "mail": "", "real_name": "", "organization": "" } cache_tile_responses = { # (projection, layers, filters, width, height, force, format): (path, ext) ("EPSG:3857", ("base",), (), 256, 256, (), "image/jpeg"): ("/disk/base3857/", "jpg"), ("EPSG:3857", ("base",), (), 256, 256, (), "image/png"): ("/disk/base3857/", "png"), } ## Available layers. layers = {\ "yhsat": { \ "name": "Yahoo Satellite", "prefix": "yhsat", # tile directory "ext": "jpg", # tile images extension "scalable": False, # could zN tile be constructed of four z(N+1) tiles "fetch": fetchers.Tile, # function that fetches given tile. should return None if tile wasn't fetched "remote_url": "http://aerial.maps.yimg.com/ximg?v=1.8&t=a&s=256&r=1&x=%s&y=%s&z=%s", "transform_tile_number": lambda z,x,y: (x,((2**(z-1)/2)-1)-y,z), "dead_tile": install_path + "yahoo_nxt.jpg", "min_zoom": 2, "max_zoom": 18, "proj": "EPSG:3857", },\ "yasat": { \ "name": "Yandex Satellite", "prefix": "yasat", # tile directory "ext": "jpg", # tile images extension "scalable": False, # could zN tile be constructed of four z(N+1) tiles "fetch": fetchers.Tile, # function that fetches given tile. should return None if tile wasn't fetched "remote_url": "http://sat01.maps.yandex.net/tiles?l=sat&v=1.22.0&x=%s&y=%s&z=%s", "transform_tile_number": lambda z,x,y: (x,y,z-1), "dead_tile": install_path + "yandex_nxt.jpg", "proj": "EPSG:3395", },\ "osm": { \ "name": "OpenStreetMap mapnik", "prefix": "osm", # tile directory "ext": "png", # tile images extension "scalable": False, # could zN tile be constructed of four z(N+1) tiles "fetch": fetchers.Tile, # function that fetches given tile. should return None if tile wasn't fetched "remote_url": "http://c.tile.openstreetmap.org/%s/%s/%s.png", "transform_tile_number": lambda z,x,y: (z-1,x,y), "proj": "EPSG:3857", "empty_color": "#F1EEE8", "cache_ttl": 864000, },\ "osm-be": { \ "name": "OpenStreetMap mapnik - Belarus", "cached": False, "scalable": False, # could zN tile be constructed of four z(N+1) tiles "fetch": fetchers.Tile, # function that fetches given tile. should return None if tile wasn't fetched "remote_url": "http://tile.latlon.org/tiles/%s/%s/%s.png", "transform_tile_number": lambda z,x,y: (z-1,x,y), "proj": "EPSG:3857", "empty_color": "#f2efe9", "data_bounding_box": (23.16722,51.25930,32.82244,56.18162), },\ "landsat": { \ "name": "Landsat from onearth.jpl.nasa.gov", "prefix": "landsat", # tile directory "ext": "jpg", # tile images extension "scalable": False, # could zN tile be constructed of four z(N+1) tiles "fetch": fetchers.WMS, # function that fetches given tile. should return None if tile wasn't fetched "remote_url": "http://onearth.jpl.nasa.gov/wms.cgi?request=GetMap&layers=global_mosaic&styles=&format=image/jpeg&", # string without srs, height, width and bbox "max_zoom": 14, "proj": "EPSG:4326", "wms_proj": "EPSG:4326", # what projection to ask from wms },\ "latlonsat": { \ "name": "Imagery from latlon.org", "prefix": "latlonsat", # tile directory "ext": "jpg", # tile images extension "scalable": False, # could zN tile be constructed of four z(N+1) tiles "fetch": fetchers.WMS, # function that fetches given tile. should return None if tile wasn't fetched "remote_url": "http://dev.latlon.org/cgi-bin/ms?FORMAT=image/jpeg&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&Layers=sat,plane&", # string without srs, height, width and bbox "max_zoom": 19, "proj": "EPSG:3857", "wms_proj": "EPSG:3857", # what projection to ask from wms },\ } twms-0.07z+git20201202+bb7c3f8/twms/twms.py000066400000000000000000000530671376171532100177250ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- # This file is part of twms. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms specified in COPYING. from __future__ import print_function, division try: from PIL import Image, ImageOps, ImageColor except ImportError: import Image, ImageOps, ImageColor import imp import os import math import sys import urllib from io import BytesIO import time import datetime sys.path.append(os.path.join(os.path.dirname(__file__))) config_path = "/etc/twms/twms.conf" if os.path.exists(config_path): try: config = imp.load_source("twms.config", config_path) except: config = imp.load_source("config", config_path) else: try: config_path = os.path.join(os.path.dirname(__file__), "twms.conf") config = imp.load_source("twms.config", config_path) except: config_path = os.path.join(os.path.realpath(sys.path[0]), "twms.conf") config = imp.load_source( "config", os.path.join(os.path.realpath(sys.path[0]), "twms.conf") ) sys.stderr.write( "Configuration file not found, using defaults from %s\n" % config_path ) sys.stderr.flush() import correctify import capabilities import fetchers # import config import bbox import bbox as bbox_utils import projections import drawing import overview from gpxparse import GPXParser from reproject import reproject try: import psyco psyco.full() except ImportError: pass OK = 200 ERROR = 500 cached_objs = {} # a dict. (layer, z, x, y): PIL image cached_hist_list = [] formats = { "image/gif": "GIF", "image/jpeg": "JPEG", "image/jpg": "JPEG", "image/png": "PNG", "image/bmp": "BMP", } mimetypes = dict(zip(formats.values(), formats.keys())) def twms_main(data): """ Do main TWMS work. data - dictionary of params. returns (error_code, content_type, resp) """ start_time = datetime.datetime.now() content_type = "text/html" resp = "" srs = data.get("srs", "EPSG:4326") gpx = data.get("gpx", "").split(",") if gpx == [""]: gpx = [] wkt = data.get("wkt", "") trackblend = float(data.get("trackblend", "0.5")) color = data.get("color", data.get("colour", "")).split(",") track = False tracks = [] if len(gpx) == 0: req_bbox = projections.from4326( (27.6518898, 53.8683186, 27.6581944, 53.8720359), srs ) else: for g in gpx: local_gpx = config.gpx_cache + "%s.gpx" % g if not os.path.exists(config.gpx_cache): os.makedirs(config.gpx_cache) if not os.path.exists(local_gpx): urllib.urlretrieve( "http://www.openstreetmap.org/trace/%s/data" % g, local_gpx ) if not track: track = GPXParser(local_gpx) req_bbox = projections.from4326(track.bbox, srs) else: track = GPXParser(local_gpx) req_bbox = bbox.add(req_bbox, projections.from4326(track.bbox, srs)) tracks.append(track) req_type = data.get("request", "GetMap") version = data.get("version", "1.1.1") ref = data.get("ref", config.service_url) if req_type == "GetCapabilities": content_type, resp = capabilities.get(version, ref) return (OK, content_type, resp) layer = data.get("layers", config.default_layers).split(",") if ("layers" in data) and not layer[0]: layer = ["transparent"] if req_type == "GetCorrections": points = data.get("points", data.get("POINTS", "")).split("=") resp = "" points = [a.split(",") for a in points] points = [(float(a[0]), float(a[1])) for a in points] req.content_type = "text/plain" for lay in layer: for point in points: resp += "%s,%s;" % tuple(correctify.rectify(config.layers[lay], point)) resp += "\n" return (OK, content_type, resp) force = data.get("force", "") if force != "": force = force.split(",") force = tuple(force) filt = data.get("filt", "") if filt != "": filt = filt.split(",") filt = tuple(filt) if layer == [""]: content_type = "text/html" resp = overview.html(ref) return (OK, content_type, resp) format = data.get("format", config.default_format).lower() format = formats.get("image/" + format, format) format = formats.get(format, format) if format not in formats.values(): return (ERROR, content_type, "Invalid format") content_type = mimetypes[format] width = 0 height = 0 resp_cache_path, resp_ext = "", "" if req_type == "GetTile": width = 256 height = 256 height = int(data.get("height", height)) width = int(data.get("width", width)) srs = data.get("srs", "EPSG:3857") x = int(data.get("x", 0)) y = int(data.get("y", 0)) z = int(data.get("z", 1)) + 1 if "cache_tile_responses" in dir(config) and not wkt and (len(gpx) == 0): if ( srs, tuple(layer), filt, width, height, force, format, ) in config.cache_tile_responses: resp_cache_path, resp_ext = config.cache_tile_responses[ (srs, tuple(layer), filt, width, height, force, format) ] resp_cache_path = resp_cache_path + "/%s/%s/%s.%s" % ( z - 1, x, y, resp_ext, ) if os.path.exists(resp_cache_path): return (OK, content_type, open(resp_cache_path, "r").read()) if len(layer) == 1: if layer[0] in config.layers: if ( config.layers[layer[0]]["proj"] == srs and width is 256 and height is 256 and not filt and not force and not correctify.has_corrections(config.layers[layer[0]]) ): local = ( config.tiles_cache + config.layers[layer[0]]["prefix"] + "/z%s/%s/x%s/%s/y%s." % (z, x / 1024, x, y / 1024, y) ) ext = config.layers[layer]["ext"] adds = ["", "ups."] for add in adds: if os.path.exists(local + add + ext): tile_file = open(local + add + ext, "r") resp = tile_file.read() return (OK, content_type, resp) req_bbox = projections.from4326(projections.bbox_by_tile(z, x, y, srs), srs) if data.get("bbox", None): req_bbox = tuple(map(float, data.get("bbox", req_bbox).split(","))) req_bbox = projections.to4326(req_bbox, srs) req_bbox, flip_h = bbox.normalize(req_bbox) box = req_bbox # print(req_bbox, file=sys.stderr) # sys.stderr.flush() height = int(data.get("height", height)) width = int(data.get("width", width)) width = min(width, config.max_width) height = min(height, config.max_height) if (width == 0) and (height == 0): width = 350 # layer = layer.split(",") imgs = 1.0 ll = layer.pop(0) if ll[-2:] == "!c": ll = ll[:-2] if wkt: wkt = "," + wkt wkt = correctify.corr_wkt(config.layers[ll]) + wkt srs = config.layers[ll]["proj"] try: result_img = getimg( box, srs, (height, width), config.layers[ll], start_time, force ) except KeyError: result_img = Image.new("RGBA", (width, height)) # width, height = result_img.size for ll in layer: if ll[-2:] == "!c": ll = ll[:-2] if wkt: wkt = "," + wkt wkt = correctify.corr_wkt(config.layers[ll]) + wkt srs = config.layers[ll]["proj"] im2 = getimg(box, srs, (height, width), config.layers[ll], start_time, force) if "empty_color" in config.layers[ll]: ec = ImageColor.getcolor(config.layers[ll]["empty_color"], "RGBA") sec = set(ec) if "empty_color_delta" in config.layers[ll]: delta = config.layers[ll]["empty_color_delta"] for tr in range(-delta, delta): for tg in range(-delta, delta): for tb in range(-delta, delta): if ( (ec[0] + tr) >= 0 and (ec[0] + tr) < 256 and (ec[1] + tr) >= 0 and (ec[1] + tr) < 256 and (ec[2] + tr) >= 0 and (ec[2] + tr) < 256 ): sec.add((ec[0] + tr, ec[1] + tg, ec[2] + tb, ec[3])) i2l = im2.load() for x in range(0, im2.size[0]): for y in range(0, im2.size[1]): t = i2l[x, y] if t in sec: i2l[x, y] = (t[0], t[1], t[2], 0) if not im2.size == result_img.size: im2 = im2.resize(result_img.size, Image.ANTIALIAS) im2 = Image.composite(im2, result_img, im2.split()[3]) # imgs/(imgs+1.)) if "noblend" in force: result_img = im2 else: result_img = Image.blend(im2, result_img, 0.5) imgs += 1.0 ##Applying filters result_img = filter.raster(result_img, filt, req_bbox, srs) # print(wkt, file=sys.stderr) # sys.stderr.flush() if wkt: result_img = drawing.wkt( wkt, result_img, req_bbox, srs, color if len(color) > 0 else None, trackblend, ) if len(gpx) > 0: last_color = None c = iter(color) for track in tracks: try: last_color = c.next() except StopIteration: pass result_img = drawing.gpx( track, result_img, req_bbox, srs, last_color, trackblend ) if flip_h: result_img = ImageOps.flip(result_img) image_content = BytesIO() if format == "JPEG": try: result_img.save( image_content, format, quality=config.output_quality, progressive=config.output_progressive, ) except IOError: result_img.save(image_content, format, quality=config.output_quality) elif format == "PNG": result_img.save( image_content, format, progressive=config.output_progressive, optimize=config.output_optimize, ) elif format == "GIF": result_img.save( image_content, format, quality=config.output_quality, progressive=config.output_progressive, ) else: ## workaround for GIF result_img = result_img.convert("RGB") result_img.save( image_content, format, quality=config.output_quality, progressive=config.output_progressive, ) resp = image_content.getvalue() if resp_cache_path: try: "trying to create local cache directory, if it doesn't exist" os.makedirs("/".join(resp_cache_path.split("/")[:-1])) except OSError: pass try: a = open(resp_cache_path, "w") a.write(resp) a.close() except (OSError, IOError): print( "error saving response answer to file %s." % (resp_cache_path), file=sys.stderr, ) sys.stderr.flush() return (OK, content_type, resp) def tile_image(layer, z, x, y, start_time, again=False, trybetter=True, real=False): """ Returns asked image. again - is this a second pass on this tile? trybetter - should we try to combine this tile from better ones? real - should we return the tile even in not good quality? """ x = x % (2 ** (z - 1)) if y < 0 or y >= (2 ** (z - 1)): return None if not bbox.bbox_is_in( projections.bbox_by_tile(z, x, y, layer["proj"]), layer.get("data_bounding_box", config.default_bbox), fully=False, ): return None global cached_objs, cached_hist_list if "prefix" in layer: if (layer["prefix"], z, x, y) in cached_objs: return cached_objs[(layer["prefix"], z, x, y)] if layer.get("cached", True): local = ( config.tiles_cache + layer["prefix"] + "/z%s/%s/x%s/%s/y%s." % (z, x / 1024, x, y / 1024, y) ) ext = layer["ext"] if "cache_ttl" in layer: for ex in [ext, "dsc." + ext, "ups." + ext, "tne"]: f = local + ex if os.path.exists(f): if os.stat(f).st_mtime < (time.time() - layer["cache_ttl"]): os.remove(f) gpt_image = False try: "trying to create local cache directory, if it doesn't exist" os.makedirs("/".join(local.split("/")[:-1])) except OSError: pass if not os.path.exists(local + "tne") and not os.path.exists(local + "lock"): if os.path.exists(local + ext): # First, look for tile in cache try: im1 = Image.open(local + ext) im1.is_ok = True return im1 except IOError: if os.path.exists(local + "lock"): return None else: os.remove(local + ext) # # Cached tile is broken - remove it if ( layer["scalable"] and (z < layer.get("max_zoom", config.default_max_zoom)) and trybetter ): # Second, try to glue image of better ones if os.path.exists(local + "ups." + ext): try: im = Image.open(local + "ups." + ext) im.is_ok = True return im except IOError: pass ec = ImageColor.getcolor( layer.get("empty_color", config.default_background), "RGBA" ) ec = (ec[0], ec[1], ec[2], 0) im = Image.new("RGBA", (512, 512), ec) im1 = tile_image(layer, z + 1, x * 2, y * 2, start_time) if im1: im2 = tile_image(layer, z + 1, x * 2 + 1, y * 2, start_time) if im2: im3 = tile_image(layer, z + 1, x * 2, y * 2 + 1, start_time) if im3: im4 = tile_image( layer, z + 1, x * 2 + 1, y * 2 + 1, start_time ) if im4: im.paste(im1, (0, 0)) im.paste(im2, (256, 0)) im.paste(im3, (0, 256)) im.paste(im4, (256, 256)) im = im.resize((256, 256), Image.ANTIALIAS) if layer.get("cached", True): try: im.save(local + "ups." + ext) except IOError: pass im.is_ok = True return im if not again: if "fetch" in layer: delta = datetime.datetime.now() - start_time delta = delta.seconds + delta.microseconds / 1000000.0 if (config.deadline > delta) or (z < 4): im = fetchers.fetch(z, x, y, layer) # Try fetching from outside if im: im.is_ok = True return im if real and (z > 1): im = tile_image( layer, z - 1, int(x / 2), int(y / 2), start_time, again=False, trybetter=False, real=True, ) if im: im = im.crop( ( 128 * (x % 2), 128 * (y % 2), 128 * (x % 2) + 128, 128 * (y % 2) + 128, ) ) im = im.resize((256, 256), Image.BILINEAR) im.is_ok = False return im else: if "fetch" in layer: delta = datetime.datetime.now() - start_time delta = delta.seconds + delta.microseconds / 1000000.0 if (config.deadline > delta) or (z < 4): im = fetchers.fetch(z, x, y, layer) # Try fetching from outside if im: im.is_ok = True return im def getimg(bbox, request_proj, size, layer, start_time, force): orig_bbox = bbox ## Making 4-corner maximal bbox bbox_p = projections.from4326(bbox, request_proj) bbox_p = projections.to4326( (bbox_p[2], bbox_p[1], bbox_p[0], bbox_p[3]), request_proj ) bbox_4 = ( (bbox_p[2], bbox_p[3]), (bbox[0], bbox[1]), (bbox_p[0], bbox_p[1]), (bbox[2], bbox[3]), ) if "nocorrect" not in force: bb4 = [] for point in bbox_4: bb4.append(correctify.rectify(layer, point)) bbox_4 = bb4 bbox = bbox_utils.expand_to_point(bbox, bbox_4) # print(bbox) # print(orig_bbox) global cached_objs H, W = size max_zoom = layer.get("max_zoom", config.default_max_zoom) min_zoom = layer.get("min_zoom", 1) zoom = bbox_utils.zoom_for_bbox( bbox, size, layer, min_zoom, max_zoom, (config.max_height, config.max_width) ) lo1, la1, lo2, la2 = bbox from_tile_x, from_tile_y, to_tile_x, to_tile_y = projections.tile_by_bbox( bbox, zoom, layer["proj"] ) cut_from_x = int(256 * (from_tile_x - int(from_tile_x))) cut_from_y = int(256 * (from_tile_y - int(from_tile_y))) cut_to_x = int(256 * (to_tile_x - int(to_tile_x))) cut_to_y = int(256 * (to_tile_y - int(to_tile_y))) from_tile_x, from_tile_y = int(from_tile_x), int(from_tile_y) to_tile_x, to_tile_y = int(to_tile_x), int(to_tile_y) bbox_im = ( cut_from_x, cut_to_y, 256 * (to_tile_x - from_tile_x) + cut_to_x, 256 * (from_tile_y - to_tile_y) + cut_from_y, ) x = 256 * (to_tile_x - from_tile_x + 1) y = 256 * (from_tile_y - to_tile_y + 1) # print(x, y, file=sys.stderr) # sys.stderr.flush() out = Image.new("RGBA", (x, y)) for x in range(from_tile_x, to_tile_x + 1): for y in range(to_tile_y, from_tile_y + 1): got_image = False im1 = tile_image(layer, zoom, x, y, start_time, real=True) if im1: if "prefix" in layer: if (layer["prefix"], zoom, x, y) not in cached_objs: if im1.is_ok: cached_objs[(layer["prefix"], zoom, x, y)] = im1 cached_hist_list.append((layer["prefix"], zoom, x, y)) # print((layer["prefix"], zoom, x, y), cached_objs[(layer["prefix"], zoom, x, y)], file=sys.stderr) # sys.stderr.flush() if len(cached_objs) >= config.max_ram_cached_tiles: del cached_objs[cached_hist_list.pop(0)] # print("Removed tile from cache", file=sys.stderr) # sys.stderr.flush() else: ec = ImageColor.getcolor( layer.get("empty_color", config.default_background), "RGBA" ) # ec = (ec[0],ec[1],ec[2],0) im1 = Image.new("RGBA", (256, 256), ec) out.paste(im1, ((x - from_tile_x) * 256, (-to_tile_y + y) * 256)) if "filter" in layer: out = filter.raster(out, layer["filter"], orig_bbox, request_proj) ## TODO: Here's a room for improvement. we could drop this crop in case user doesn't need it. out = out.crop(bbox_im) if "noresize" not in force: if (H == W) and (H == 0): W, H = out.size if H == 0: H = out.size[1] * W // out.size[0] if W == 0: W = out.size[0] * H // out.size[1] # bbox = orig_bbox quad = [] trans_needed = False for point in bbox_4: x = (point[0] - bbox[0]) / (bbox[2] - bbox[0]) * (out.size[0]) y = (1 - (point[1] - bbox[1]) / (bbox[3] - bbox[1])) * (out.size[1]) x = int(round(x)) y = int(round(y)) if (x != 0 and x != out.size[0]) or (y != 0 and y != out.size[1]): trans_needed = True quad.append(x) quad.append(y) if trans_needed: quad = tuple(quad) out = out.transform((W, H), Image.QUAD, quad, Image.BICUBIC) elif (W != out.size[0]) or (H != out.size[1]): "just resize" out = out.resize((W, H), Image.ANTIALIAS) # out = reproject(out, bbox, layer["proj"], request_proj) return out twms-0.07z+git20201202+bb7c3f8/yahoo_nxt.jpg000066400000000000000000000112401376171532100200640ustar00rootroot00000000000000JFIFC    $.' ",#(7),01444'9=82<.342  }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz?((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((֊((EQEQEQEQEQE~b;?6݌:Fw WЕ.@),3BE3``5-wz1@5[ -vDe9Pw&ߎ ^AḌpXE=%Cӌ(ǵCqrbgD@俭MGJ:QEQEQEV6y &.89]svشk7hmTNGf(7ʳ2xsEA#=8rQM'&|&Qpp1T3iS$hcWH"E%8sVF49QX*(sG=),q .ǢtU\}ƠѶrYG;ZgD!: /TIEt3DwH#ub+{vb[hG?d\,hCL4Y cy\UEƤX#ʡܿ&z |qv{;bRP]ġ qZ85 oy5P1H~fhQʧ' 1JҾkd9ʍnǃe|grOᐲ|'~:bm"KmC\$NWo8zDӚ_5ḁlcMg袊(((x l)hbbN98H~k<HαI JomaOÌ~QZɄFؕlJ`45Y'f8#gZyG"  8x})W\R*~' -5XeuyEl33V%\yWQ^ê٭ϐҰg!wnM}b3'#Fc<.G QuX/!fbgQЂHGPɩH <." 2sxԷz1Y, цYfQu=N:}Q<I(eBC>r3 XSrn` }8ʓҬEQEQEQE0zVChHm,fGRupG9\w{K˪ʜcSˤ3Gr#w&3mt4HLml7`xf BnCyE`c$2Qd`PGՊY W]9AcZV[u4Ӭܸv+0t1ho ذ@m@"+#7nzEKi/vE·,R/w _OžV)F^.MxoU4iYE@g,yåV6ybݽCV1q ȝ%0~Xzd,rڐ4XprqIyZm*IdiS;K2g I#ޣ>Žmm+|y~OQEQEQEQE(u:u(:QEފ((((Zf[sɑK6*#Xȟtݰ~iu&S|{ Fz dM}^B~}rdc9[jvw3m>VPW8'ϥ/8}۸Pͬggu *dQnHm}ʘ۟"f'X HJҌ0ʂOB{SIu 3 cK@Rx9AP ^Ķ 1Ve`d-uز:2ƪ)k(pLNޙrsTjvLp@l/M2O)#bK򁌓V`;DFy:EIEQEQE}zUK=5,Y!HQ0a`Iƥ "؃鍽0jp5a7h# ڄH[3[*0sLfӴ$EUMn&;npyc}9y'co8?rwFw"7'y{2K3\ʵuuFB wg<}$zO67Hd0\cӅze퉜n`ݒ={m"h.,[>mۋ}t)#{k" `lg?,3ȓAюၞ2'qP &#wGoBXT<r3W-b Ix^jz(((;bhnlDhcpOZljp]LX˰N~c52 U5>bLS1ɶGmS`:r"[y7m^F1MZYk3` NH=94nhX)fx>3vI}dM۬*cЙ2 Co>Cj!d; 7 |90AImඁ晱,(Ul0}if2ERefV5zKhmp "F2IS-ռZ|y4y[\)sY\ڙ"sd(9Z[,@\28oPSXS>N}Ǟe:cY/ld~T[PhԢ`'$uZV! ?x GۣFY6.3=0z{Tr?eQ"JdvrI=qSi#0Hh|sg֝ >G?*g4QEQEQE}*,i8Aю{q/F7k\,vN6y4\4 kseF@`1սqZz\ܳOEđƻB Ǿ9#²FZ-d2Eso S?{j6Q6mDSWN<桓W!Kpb&m֜w6+-¹xRĜG*AucԵI,c}v v00H'+WWG[Yf]$8W"nN%ƧynI;P[mJH㈕*Hpq. 8n F%ڮ'c=1sPM!f p9NO u\,nJBۄ_nNH$8#֙q\4,{@Sx53_]LʂXB@ep~0kv99r>(((..#̨qw6?8?;䁜wEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWtwms-0.07z+git20201202+bb7c3f8/yandex_nxt.jpg000066400000000000000000000005311376171532100202360ustar00rootroot00000000000000 404 - Not Found

404 - Not Found