Skip to content

Instantly share code, notes, and snippets.

@aleks-mariusz
Last active March 18, 2017 18:04
Show Gist options
  • Save aleks-mariusz/56c5cb6a244fda3a3c8367f8956ae390 to your computer and use it in GitHub Desktop.
Save aleks-mariusz/56c5cb6a244fda3a3c8367f8956ae390 to your computer and use it in GitHub Desktop.
this will download the geojson for ordinance survey's maps, parse it and create mobac-profiles for their explorer/landranger series of maps
#!/usr/bin/env python
import argparse
import itertools
import json
import math
import os
import re
import sys
import urllib2
from convertbng.util import convert_lonlat
reload(sys)
sys.setdefaultencoding('utf-8')
default_atlas_format = 'MBTiles'
default_map_source = 'foo' # replace with the name of the source of your explorer maps
default_tile_size = 256 # pixels x pixels
url = {
'explorer': 'https://api.ordnancesurvey.co.uk/osl/v1/mapsheet/explorer?bbox=0,0,700000,1300000,27700&tsrs=27700',
'landranger': 'https://api.ordnancesurvey.co.uk/osl/v1/mapsheet/landranger?bbox=0,0,700000,1300000,27700&tsrs=27700',
}
zoom = {
'explorer': 16,
'landranger': 14,
}
def lng_lat_to_tile_pixels(lng, lat, zoom, tile_size=default_tile_size):
lat = math.radians(lat)
n = 2.0 ** zoom
xtile_pixels = tile_size*(lng + 180.0) / 360.0 * n
ytile_pixels = tile_size*(1.0 - math.log(math.tan(lat) + (1.0 / math.cos(lat))) / math.pi) / 2.0 * n
return xtile_pixels, ytile_pixels
def make_mobac_profile(bbox, coords, zooms, name, base, src, format):
# bbox/coords are in lat/lng format, this function will convert to appropriate tile-x/tile-y for given zoom(s)
name = name.replace('/',' - ')
with open('{0}{1}.xml'.format(base,name),'w') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n')
f.write('<atlas version="1" name="{0}" outputFormat="{1}">\n'.format('Ordnance Survey '+args.maptype.capitalize()+' Map '+name,format))
f.write(' <Layer name="{0}">\n'.format(name))
for z in zooms:
min_tile = lng_lat_to_tile_pixels(bbox[0], bbox[3], z)
max_tile = lng_lat_to_tile_pixels(bbox[2], bbox[1], z)
for i,coord_list in enumerate(coords):
f.write(' <PolygonMap name="{0}_{1} {2:02d}" zoom="{2}" mapSource="{3}" minTileCoordinate="{4}/{5}" maxTileCoordinate="{6}/{7}">\n'.format(
name,
i,
z,
src,
int(math.floor(min_tile[0])),
int(math.floor(min_tile[1])),
int(math.ceil(max_tile[0])),
int(math.ceil(max_tile[1])),
))
f.write(' <polygon>\n')
for c in coord_list:
f.write(' <point>{0}/{1}</point>\n'.format(*map(int,lng_lat_to_tile_pixels(*c, zoom=z))))
f.write(' </polygon>\n')
f.write(' </PolygonMap>\n')
f.write(' </Layer>\n')
f.write('</atlas>\n')
if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('--outputdir', action='store', dest='output_dir', help='dir to create mobac profiles within (def: cwd)')
parser.add_argument('--zoomlevels', action='store', dest='zoomlevels', help='comma separated list of zooms (def: lr:14 exp:16)')
parser.add_argument('--regex', action='store', dest='regex', help='regular expression to filter')
parser.add_argument('--mapsrc', action='store', dest='mapsrc', help='mobac-recognized map source')
parser.add_argument('--format', action='store', dest='format' , help='format to write profile type (def: {0})'.format(default_atlas_format))
parser.add_argument('--type', action='store', dest='maptype', help='either explorer or landranger', required=True)
args = parser.parse_args()
if args.maptype.lower() not in ['explorer','landranger']:
print('ERROR: only map type of Explorer or LandRanger is supported, not {0}'.format(args.maptype))
fp_base = 'mobac-profile-'
if args.output_dir:
if not os.path.exists(args.output_dir):
os.makedirs(args.output_dir)
fp_base = os.path.join(args.output_dir, fp_base)
if not args.zoomlevels:
zoom_levels = [ zoom[args.maptype.lower()] ]
else:
zoom_levels = map(int, args.zoomlevels.split(','))
response = urllib2.urlopen(url[args.maptype.lower()])
maps_info = json.load(response)
if 'features' not in maps_info:
print('ERROR: invalid GeoJSON received')
sys.exit(1)
for i,f in enumerate(maps_info['features']):
p = f['properties']
map_name = '{0} - {1}'.format(p['SHEET'], p['TITLE']) # e.g., OL22 - New Forest
if args.regex and not re.match(args.regex, map_name):
continue
g = f['geometry']
error_found = False
try:
assert len(g['bbox']) == 6
ll_e, ll_n, _, ur_e, ur_n, _ = map(int,map(round,g['bbox']))
bbox = tuple(itertools.chain.from_iterable(zip(*(convert_lonlat([ll_e, ur_e], [ll_n, ur_n])))))
except AssertionError:
print('WARN: on feature #{0}, didnt see 6 elements in the bbox array'.format(i))
# print('{0}'.format(g['bbox']))
error_found = True
lngs_lats = []
for j,c in enumerate(g['coordinates']):
# print('INFO: parsing map #{0} coordinates {1}'.format(i,j))
if len(c) == 1:
c = c[0]
eastings, northings = zip(*[ (e,n) for e,n,_ in c ])
eastings = map(int,map(round,eastings))
northings = map(int,map(round,northings))
lngs_lats.append(zip(*convert_lonlat(eastings, northings)))
if not error_found:
print('INFO: creating profile for map "{0}"'.format(map_name))
make_mobac_profile(bbox, lngs_lats, zoom_levels, map_name, fp_base, args.mapsrc if args.mapsrc else default_map_source, args.format if args.format else default_atlas_format)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment