# http://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases() # noqa
import logging
import os
from collections import namedtuple
import os.path as op
import numpy as num
from pyrocko import util, config
from pyrocko import orthodrome as od
logger = logging.getLogger('pyrocko.dataset.geonames')
GeoName = namedtuple('GeoName', '''
geonameid
name
asciiname
alt_names
lat
lon
feature_class
feature_code
country_code
alt_country_code
admin1_code
admin2_code
admin3_code
admin4_code
population
elevation
dem
timezone
modification_date'''.split())
GeoName2 = namedtuple('GeoName2', '''
name
asciiname
lat
lon
population
feature_code
'''.split())
base_url = 'https://mirror.pyrocko.org/download.geonames.org/export/dump/'
def download_file(fn, dirpath):
import urllib.request
url = base_url + '/' + fn
fpath = op.join(dirpath, fn)
logger.info('starting download of %s' % url)
util.ensuredirs(fpath)
f = urllib.request.urlopen(url)
fpath_tmp = fpath + '.%i.temp' % os.getpid()
g = open(fpath_tmp, 'wb')
while True:
data = f.read(1024)
if not data:
break
g.write(data)
g.close()
f.close()
os.rename(fpath_tmp, fpath)
logger.info('finished download of %s' % url)
def positive_region(region):
west, east, south, north = [float(x) for x in region]
assert -180. - 360. <= west < 180.
assert -180. < east <= 180. + 360.
assert -90. <= south < 90.
assert -90. < north <= 90.
if east < west:
east += 360.
if west < -180.:
west += 360.
east += 360.
return (west, east, south, north)
def ascii_str(u):
return u.encode('ascii', 'replace')
def load(zfn, fn, minpop=1000000, region=None):
geonames_dir = config.config().geonames_dir
filepath = op.join(geonames_dir, zfn or fn)
if not os.path.exists(filepath):
download_file(zfn or fn, geonames_dir)
if region:
w, e, s, n = positive_region(region)
if zfn is not None:
import zipfile
z = zipfile.ZipFile(filepath, 'r')
f = z.open(fn, 'r')
else:
z = None
f = open(filepath, 'rb')
for line in f:
t = line.split(b'\t')
pop = int(t[14])
if minpop <= pop:
lat = float(t[4])
lon = float(t[5])
feature_code = str(t[7].decode('ascii'))
if not region or (
(w <= lon <= e or w <= lon + 360. <= e)
and (s <= lat <= n)):
yield GeoName2(
t[1].decode('utf8'),
str(t[2].decode('utf8').encode('ascii', 'replace')
.decode('ascii')),
lat, lon, pop, feature_code)
f.close()
if z is not None:
z.close()
g_cities = {}
def load_all_keep(zfn, fn, minpop=1000000, region=None, exclude=()):
if (zfn, fn) not in g_cities:
g_cities[zfn, fn] = list(load(zfn, fn, minpop=0))
if region:
w, e, s, n = positive_region(region)
return [c for c in g_cities[zfn, fn]
if (minpop <= c.population and
((w <= c.lon <= e or w <= c.lon + 360. <= e)
and (s <= c.lat <= n)) and
c.feature_code not in exclude)]
else:
return [c for c in g_cities[zfn, fn] if (
minpop <= c.population and c.feature_code not in exclude)]
def get_cities_region(region=None, minpop=0):
return load_all_keep(
'cities1000.zip', 'cities1000.txt',
region=region,
minpop=minpop,
exclude=('PPLX',))
def get_cities_by_name(name):
cities = get_cities_region()
candidates = []
for c in cities:
if c.asciiname.lower() == name.lower():
candidates.append(c)
return candidates
def get_cities(lat, lon, radius, minpop=0):
region = od.radius_to_region(lat, lon, radius)
cities = get_cities_region(region, minpop=minpop)
clats = num.array([c.lat for c in cities])
clons = num.array([c.lon for c in cities])
dists = od.distance_accurate50m_numpy(lat, lon, clats, clons)
order = num.argsort(dists)
cities_sorted = [cities[i] for i in order if dists[i] < radius]
return cities_sorted
|