# ipv4map.py - no copyright 2011 0x2620.org - public domain from __future__ import division import Image import ImageDraw import math import os import ox import pygeoip # fixme: pow(a, b) -> a ** b geoip = pygeoip.GeoIP('../dat/GeoLiteCity.dat', pygeoip.MEMORY_CACHE) path = '../../../../oxjs.org/source/Ox.Geo/png/flags/' projection = { 'U': [[0, 2, 3, 1], 'DUUC'], # Square 0 1 Segment U = 0 3 = 0 1 E F = D C 'D': [[0, 1, 3, 2], 'UDDA'], # 2 3 1 2 3 2 D C U U 'C': [[3, 2, 0, 1], 'ACCU'], # 4 7 8 B 'A': [[3, 1, 0, 2], 'CAAD'] # 5 6 9 A } def get_country_code_by_ip(ip): replace = {'A1': 'NTHH', 'A2': 'NTHH', 'AN': 'ANHH', 'AP': 'FM', 'O1': 'NTHH'} data = geoip.record_by_addr(ip) if not ip.startswith('0.') else None country_code = data['country_code'] if data and 'country_code' in data else 'NTHH' return replace[country_code] if country_code in replace else country_code def get_flag_image(county_code, size): flag_size = str(int(pow(4, math.ceil(math.log(size, 2) / 2)))) flag_image = Image.open(path + flag_size + '/' + county_code + '.png') if size != flag_size: flag_image = flag_image.resize((size, size), Image.ANTIALIAS) return flag_image def get_ip_by_n(n): ip = [] for i in range(4): ip.append(str(int(n / pow(256, 3 - i)) % 256)) return '.'.join(ip) def get_n_by_ip(ip): n = 0 for i, v in enumerate(ip.split('.')): n += int(v) * pow(256, 3 - i) return n def get_tile_by_ip(ip, z): n = get_n_by_ip(ip) last_n = n + pow(4, 16 - z) - 1 last_ip = get_ip_by_n(last_n) return '../png/tiles/%s/%s-%s.png' % (ip.split('.')[0], ip, last_ip) def get_xy_by_ip(ip, z, w='U'): x, y = 0, 0 n = int(get_n_by_ip(ip) / pow(4, 8 - z)) for i in range(8 + z): p2 = pow(2, 7 + z - i) p4 = pow(4, 7 + z - i) q = int(n / p4) xy = projection[w][0][q] x += xy % 2 * p2 y += int(xy / 2) * p2 n -= q * p4 w = projection[w][1][q] return [x, y] def render_flags(): # renders an image of 256 flags image = Image.new('RGB', (1024, 1024)) font = '../ttf/DejaVuSansMonoBold.ttf' countries = ox.file.read_json('../../../../oxjs.org/oxjs/source/Ox.Geo/json/Ox.Geo.json') countries = filter(lambda x: len(x['code']) == 2 and not x['code'] in ['UK', 'XK'], countries) for i, country in enumerate(sorted(countries, key=lambda x: x['code'])): flag = get_flag_image(country['code'], 64) x, y = i % 16 * 64, int(i / 16) * 64 image.paste(flag, (x, y, x + 64, y + 64)) ox.image.drawText(image, (x + 5, y + 5), country['code'], font, 8, (64, 64, 64)) ox.image.drawText(image, (x + 4, y + 4), country['code'], font, 8, (192, 192, 192)) image.save('../png/flags.png') def render_map(): # renders images of the full map if not os.path.exists('../png/ipv4map16.png'): mapfile = '../png/ipv4map16384.png' mapimage = Image.new('RGB', (16384, 16384)) for a in range(0, 256, 16): print '%d.0.0.0' % a image = Image.open('../png/map/%d.0.0.0-%d.255.255.255.png' % (a, a + 15)) x, y = map(lambda x: int(x / 4096) * 4096, get_xy_by_ip('%d.0.0.0' % a, 6)) mapimage.paste(image.resize((4096, 4096), Image.ANTIALIAS), (x, y)) mapimage.save(mapfile) for s in [4096, 1024, 16]: mapimage.resize((s, s), Image.ANTIALIAS).save(mapfile.replace('16384', str(s))) def render_square(values, maxlen, w): # recursively renders a square region, given a list of country codes length = len(values) if length > 4: square_length = int(length / 4) square_values = map(lambda x: values[x * square_length:(x + 1) * square_length], range(4)) values = [] for i, v in enumerate(square_values): values.append(render_square(v, maxlen, projection[w][1][i])) equal = values[0] == values[1] == values[2] == values[3] if length < maxlen and equal: return values[0] else: order = projection[w][0] image_size = int(math.sqrt(length) * 16) flag_size = image_size if equal else int(image_size / 2) image = Image.new('RGB', (image_size, image_size)) for i, value in enumerate([values[0]] if equal else values): flag = get_flag_image(value, flag_size) if isinstance(value, str) else value image.paste(flag, (order[i] % 2 * flag_size, int(order[i] / 2) * flag_size)) if length == pow(4, 4): image.save('../png/_tmp/' + str(values) + '.png') return image def render_tile(ip, z): # recursively renders the map tile for a given ip at a given zoom level n = get_n_by_ip(ip) file = get_tile_by_ip(ip, z) if not os.path.exists(file): tiles_n = map(lambda x: n + x * pow(4, 15 - z), range(4)) tiles_ip = map(lambda x: get_ip_by_n(x), tiles_n) tiles = map(lambda x: render_tile(x, z + 1), tiles_ip) image = Image.new('RGB', (256, 256)) x, y = map(lambda x: int(x / 256) * 256, get_xy_by_ip(ip, z)) for i in range(4): x_, y_ = map(lambda x: int(x / 128) * 128, get_xy_by_ip(tiles_ip[i], z)) image.paste(tiles[i].resize((128, 128), Image.ANTIALIAS), (x_ - x, y_ - y)) image.save(file) else: image = Image.open(file) return image def render_tiles(): # renders all tiles at the maximum zoom level w = '' for v in projection['U'][1]: w += projection[v][1] file256 = '../png/ipv4map16384.png' for a in range(256): print '%d.0.0.0' % a if a % 16 == 0: values = [] file = '../png/map/%d.0.0.0-%d.255.255.255.png' % (a, a + 15) if not os.path.exists(file): for b in range(256): for c in range(256): values.append(get_country_code_by_ip('%d.%d.%d.0' % (a, b, c))) if a % 16 == 15: image = render_square(values, len(values), w[int(a / 16)]).save(file) if a % 16 == 15: image = Image.open(file) for a_ in range(a - 15, a + 1): for b in range(256): file = '../png/tiles/%d/%d.%d.0.0-%d.%d.255.255.png' % (a_, a_, b, a_, b) if not os.path.exists(file): x, y = map(lambda x: int(x % 16384 / 256) * 256, get_xy_by_ip('%d.%d.0.0' % (a_, b), 8)) dirs = os.path.split(file)[0] if dirs and not os.path.exists(dirs): os.makedirs(dirs) image.crop((x, y, x + 256, y + 256)).save(file) def render_projection(): # renders an image of the map projection image = Image.new('RGB', (1024, 1024)) draw = ImageDraw.Draw(image) font = '../ttf/DejaVuSansCondensedBold.ttf' for i in range(256): x, y = map(lambda x: int(x / 64) * 64, get_xy_by_ip('%d.0.0.0' % i, 2)) rgb = ox.getRGB((i * 360 / 256, 1, 0.5)) draw.rectangle((x, y, x + 64, y + 64), fill=rgb) ox.image.drawText(image, (x + 4, y + 4), str(i), font, 8, (0, 0, 0)) draw.rectangle((x + 30, y + 30, x + 34, y + 33), fill=0) if i: draw.line((x_ + 32, y_ + 32, x + 32, y + 32), fill=0, width=4) x_, y_ = x, y image.save('../png/projection.png') if __name__ == '__main__': # qlmanage -t -s 16384 -o . ../../../../oxjs.org/tools/geo/svg/icons/NTHH.svg render_flags() render_projection() render_tiles() render_tile('0.0.0.0', 0) render_map() # http://s3.amazonaws.com/alexa-static/top-1m.csv.zip