192 lines
7.6 KiB
Text
192 lines
7.6 KiB
Text
|
# 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
|