Last active
March 18, 2017 18:04
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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