创建 ASCII 艺术世界地图

鉴于此 GeoJSON 文件,我想呈现 ASCII 艺术世界地图。

我的基本方法是将 GeoJSON 加载到Shapely 中,使用pyproj将点转换为墨卡托,然后对我的 ASCII 艺术网格的每个字符的几何图形进行命中测试。

当以本初子午线为中心时,它看起来(编辑:大部分)OK:

http://img.mukewang.com/61c13e700001eca716261099.jpg

但以纽约市 ( lon_0=-74)为中心,它突然失控了:

http://img1.mukewang.com/61c13e7d0001986816151079.jpg

我很确定我在这里的预测有问题。(并且将 ASCII 地图坐标转换为纬度/经度可能比转换整个几何更有效,但我不确定如何。)


import functools

import json

import shutil

import sys


import pyproj

import shapely.geometry

import shapely.ops



# Load the map

with open('world-countries.json') as f:

  countries = []

  for feature in json.load(f)['features']:

    # buffer(0) is a trick for fixing polygons with overlapping coordinates

    country = shapely.geometry.shape(feature['geometry']).buffer(0)

    countries.append(country)


mapgeom = shapely.geometry.MultiPolygon(countries)


# Apply a projection

tform = functools.partial(

  pyproj.transform,

  pyproj.Proj(proj='longlat'),  # input: WGS84

  pyproj.Proj(proj='webmerc', lon_0=0),  # output: Web Mercator

)

mapgeom = shapely.ops.transform(tform, mapgeom)


# Convert to ASCII art

minx, miny, maxx, maxy = mapgeom.bounds

srcw = maxx - minx

srch = maxy - miny

dstw, dsth = shutil.get_terminal_size((80, 20))


for y in range(dsth):

  for x in range(dstw):

    pt = shapely.geometry.Point(

      (srcw*x/dstw) + minx,

      (srch*(dsth-y-1)/dsth) + miny  # flip vertically

    )

    if any(country.contains(pt) for country in mapgeom):

      sys.stdout.write('*')

    else:

      sys.stdout.write(' ')

  sys.stdout.write('\n')


皈依舞
浏览 252回答 1
1回答

手掌心

我在底部进行编辑,发现新问题(为什么没有加拿大和Shapely和Pyproj的不可靠性)尽管它不能完全解决问题,但我相信这种态度比使用 pyproc 和 Shapely 更有潜力,并且在未来,如果你做更多的 Ascii 艺术,会给你更多的可能性和灵活性。首先我会写优点和缺点。PS:最初我想在您的代码中找到问题,但是我在运行它时遇到了问题,因为 pyproj 返回了一些错误。优点1)我能够提取所有点(加拿大确实缺少)并旋转图像2)处理速度非常快,因此您可以创建动画 Ascii 艺术。3)打印一次完成,无需循环缺点(已知问题,可解决)1)这种态度显然不能正确转换地理坐标 - 太平面了,它应该看起来更球形2)我没有花时间试图找出填充边框的解决方案,所以只有边框有'*'。因此这种态度需要找到算法来填充国家。我认为这不应该是问题,因为 JSON 文件包含分隔的国家3)除了 numpy - opencv(您可以使用 PIL 代替)和 Colorama 之外,您还需要 2 个额外的库,因为我的示例是动画的,我需要通过将光标移动到 (0,0) 而不是使用 os.system( 'cls')4)我让它只在python 3 中运行。在 python 2 中它也可以工作,但我在使用 sys.stdout.buffer 时遇到错误将终端上的字体大小更改为最低点,以便打印的字符适合终端。字体更小,分辨率更高动画应该看起来像地图在“旋转”&nbsp;我用了一点你的代码来提取数据。步骤在评论中import jsonimport sysimport numpy as npimport coloramaimport sysimport timeimport cv2#understand terminal_size as how many letters in X axis and how many in Y axis. Sorry not good nameif len(sys.argv)>1:&nbsp; &nbsp;&nbsp; &nbsp; terminal_size = (int(sys.argv[1]),int(sys.argv[2]))else:&nbsp; &nbsp; terminal_size=(230,175)with open('world-countries.json') as f:&nbsp; &nbsp; countries = []&nbsp; &nbsp; minimal = 0 # This can be dangerous. Expecting negative values&nbsp; &nbsp; maximal = 0 # Expecting bigger values than 0&nbsp; &nbsp; for feature in json.load(f)['features']: # getting data&nbsp; - I pretend here, that geo coordinates are actually indexes of my numpy array&nbsp; &nbsp; &nbsp; &nbsp; indexes = np.int16(np.array(feature['geometry']['coordinates'][0])*2)&nbsp; &nbsp; &nbsp; &nbsp; if indexes.min()<minimal:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; minimal = indexes.min()&nbsp; &nbsp; &nbsp; &nbsp; if indexes.max()>maximal:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; maximal = indexes.max()&nbsp; &nbsp; &nbsp; &nbsp; countries.append(indexes)&nbsp;&nbsp; &nbsp; countries = (np.array(countries)+np.abs(minimal)) # Transform geo-coordinates to image coordinatescorrection = np.abs(minimal) # because geo-coordinates has negative values, I need to move it to 0 - xaxiscolorama.init()def move_cursor(x,y):&nbsp; &nbsp; print ("\x1b[{};{}H".format(y+1,x+1))move = 0 # 'rotate' the globefor i in range(1000):&nbsp; &nbsp; image = np.zeros(shape=[maximal+correction+1,maximal+correction+1]) #creating clean image&nbsp; &nbsp; move -=1 # you need to rotate with negative values&nbsp; &nbsp; # because negative one are by numpy understood. Positive one will end up with error&nbsp; &nbsp; for i in countries: # VERY STRANGE,because parsing the json, some countries has different JSON structure&nbsp; &nbsp; &nbsp; &nbsp; if len(i.shape)==2:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; image[i[:,1],i[:,0]+move]=255 # indexes that once were geocoordinates now serves to position the countries in the image&nbsp; &nbsp; &nbsp; &nbsp; if len(i.shape)==3:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; image[i[0][:,1],i[0][:,0]+move]=255&nbsp; &nbsp; cut = np.where(image==255) # Bounding box&nbsp; &nbsp; if move == -1: # creating here bounding box - removing empty edges - from sides and top and bottom - we need space. This needs to be done only once&nbsp; &nbsp; &nbsp; &nbsp; max_x,min_x = cut[0].max(),cut[0].min()&nbsp; &nbsp; &nbsp; &nbsp; max_y,min_y = cut[1].max(),cut[1].min()&nbsp; &nbsp; new_image = image[min_x:max_x,min_y:max_y] # the bounding box&nbsp; &nbsp; new_image= new_image[::-1] # reverse, because map is upside down&nbsp; &nbsp; new_image = cv2.resize(new_image,terminal_size) # resize so it fits inside terminal&nbsp; &nbsp; ascii = np.chararray(shape = new_image.shape).astype('|S4') #create container for asci image&nbsp; &nbsp; ascii[:,:]='' #chararray contains some random letters - dunno why... cleaning it&nbsp; &nbsp; ascii[:,-1]='\n' #because I pring everything all at once, I am creating new lines at the end of the image&nbsp; &nbsp; new_image[:,-1]=0 # at the end of the image can be country borders which would overwrite '\n' created one step above&nbsp; &nbsp; ascii[np.where(new_image>0)]='*' # transforming image array to chararray. Better to say, anything that has pixel value higher than 0 will be star in chararray mask&nbsp; &nbsp; move_cursor(0,0) # 'cleaning' the terminal for new animation&nbsp; &nbsp; sys.stdout.buffer.write(ascii) # print into terminal&nbsp; &nbsp; time.sleep(0.025) # FPS也许最好解释一下代码中的主要算法是什么。我喜欢尽可能使用 numpy。整件事是我假装图像中的坐标,或者它可能是什么(在你的情况下地理坐标)是矩阵索引。然后我有 2 个矩阵 - Real Image 和 Charray 作为掩码。然后我在真实图像中获取有趣像素的索引,并为 Charray Mask 中的相同索引分配我想要的任何字母。多亏了这一点,整个算法不需要一个循环。关于未来的可能性想象一下,您还将获得有关地形(高度)的信息。假设您以某种方式创建了世界地图的灰度图像,其中灰色阴影表示高度。这种灰度图像的形状为 x,y。您将准备形状 = [x,y,256] 的3Dmatrix。对于 3D 矩阵中 256 个层中的每一层,您分配一个字母“....;;;;### 等等”来表示阴影。准备好后,您可以拍摄灰度图像,其中任何像素实际上都有 3 个坐标:x、y 和阴影值。因此,您将从灰度地图图像中获得3 个索引数组-> x,y,shade。您的新 charray 将简单地提取带有图层字母的3Dmatrix,因为:#Preparation phasex,y = grayscale.shape3Dmatrix = np.chararray(shape = [x,y,256])table = '&nbsp; &nbsp; ......;;;;;;;###### ...'for i in range(256):&nbsp; &nbsp; 3Dmatrix[:,:,i] = table[i]x_indexes = np.arange(x*y)y_indexes = np.arange(x*y)chararray_image = np.chararray(shape=[x,y])# Ready to print...shades = grayscale.reshape(x*y)chararray_image[:,:] = 3Dmatrix[(x_indexes ,y_indexes ,shades)].reshape(x,y)因为这个过程没有循环,你可以一次打印chararray,你实际上可以以巨大的FPS将电影打印到终端例如,如果你有地球旋转的镜头,你可以制作这样的东西 -(250*70 个字母),渲染时间 0.03658s您当然可以将其发挥到极致并在您的终端中实现超分辨率,但由此产生的 FPS 并不是那么好:0.23157s,大约是 4-5 FPS。有趣的是,这种态度FPS是巨大的,但终端根本无法处理打印,所以这种低FPS是由于终端的限制而不是计算的限制,因为这种高分辨率的计算需要0.00693s,即144 FPS。与上述某些陈述相矛盾我不小心打开了原始 json 文件,发现有 CANADA 和 RUSSIA 具有完全正确的坐标。我错误地依赖于结果中我们都没有加拿大的事实,所以我希望我的代码没问题。在 JSON 中,数据具有不同的 NOT-UNIFIED 结构。俄罗斯和加拿大有“Multipolygon”,因此您需要对其进行迭代。这是什么意思?不要依赖 Shapely 和 pyproj。显然他们不能提取一些国家,如果他们不能可靠地做到这一点,你不能指望他们做任何更复杂的事情。修改代码后,一切正常代码:这是正确加载文件的方法...with open('world-countries.json') as f:&nbsp; &nbsp; countries = []&nbsp; &nbsp; minimal = 0&nbsp; &nbsp; maximal = 0&nbsp; &nbsp; for feature in json.load(f)['features']: # getting data&nbsp; - I pretend here, that geo coordinates are actually indexes of my numpy array&nbsp; &nbsp; &nbsp; &nbsp; for k in range((len(feature['geometry']['coordinates']))):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; indexes = np.int64(np.array(feature['geometry']['coordinates'][k]))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if indexes.min()<minimal:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; minimal = indexes.min()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if indexes.max()>maximal:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; maximal = indexes.max()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; countries.append(indexes)&nbsp;...
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python