axi/examples/osm.py

266 lines
8.0 KiB
Python

from shapely import geometry, ops, affinity
import axi
import math
import osm2shapely
import sys
import logging
logging.basicConfig()
HOME = 35.768616, -78.844005
RALEIGH = 35.777486, -78.635794
DURHAM = 35.999392, -78.919217
PARIS = 48.856744, 2.351248
HOLBROOK = 32.422131, -80.713289
FORMLABS = 42.374414, -71.087908
PINE_RIDGE_ROAD = 42.427164, -71.143553
TIMES_SQUARE = 40.758582, -73.985066
LAT, LNG = TIMES_SQUARE
LANDSCAPE = False
PAGE_WIDTH_IN = 12
PAGE_HEIGHT_IN = 8.5
MAP_WIDTH_KM = 1.61
LANE_WIDTH_M = 3.7
EARTH_RADIUS_KM = 6371
WEIGHTS = {
'motorway': 2,
'motorway_link': 2,
'trunk_link': 2,
'trunk': 2,
'primary_link': 1.75,
'primary': 1.75,
'secondary': 1.5,
'secondary_link': 1.5,
'tertiary_link': 1.25,
'tertiary': 1.25,
'living_street': 1,
'unclassified': 1,
'residential': 1,
# 'service': 0,
# 'railway': 0,
# 'pedestrian': 0,
# 'footway': 0,
# 'natural': 0,
}
def paths_to_shapely(paths):
return geometry.MultiLineString(paths)
def shapely_to_paths(g):
if isinstance(g, geometry.Point):
return []
elif isinstance(g, geometry.LineString):
return [list(g.coords)]
elif isinstance(g, (geometry.MultiPoint, geometry.MultiLineString, geometry.MultiPolygon, geometry.collection.GeometryCollection)):
paths = []
for x in g:
paths.extend(shapely_to_paths(x))
return paths
elif isinstance(g, geometry.Polygon):
paths = []
paths.append(list(g.exterior.coords))
for interior in g.interiors:
paths.extend(shapely_to_paths(interior))
return paths
else:
raise Exception('unhandled shapely geometry: %s' % type(g))
def follow(g, step):
result = []
d = step
e = 1e-3
l = g.length
while d < l:
p = g.interpolate(d)
a = g.interpolate(d - e)
b = g.interpolate(d + e)
angle = math.atan2(b.y - a.y, b.x - a.x)
result.append((p.x, p.y, angle))
d += step
return result
def hatch(g, angle, step):
print g.area
x0, y0, x1, y1 = g.bounds
d = max(x1 - x0, y1 - y0) * 2
lines = []
x = 0
while x < d:
lines.append(geometry.LineString([(x - d / 2, -d / 2), (x - d / 2, d / 2)]))
x += step
if not lines:
return None
lines = geometry.collection.GeometryCollection(lines)
lines = affinity.rotate(lines, angle)
lines = affinity.translate(lines, (x0 + x1) / 2, (y0 + y1) / 2)
return g.intersection(lines)
def crop(g, w, h):
return g.intersection(geometry.Polygon(box(w, h)))
def box(w, h):
w *= 0.5
h *= 0.5
return [(-w, -h), (w, -h), (w, h), (-w, h), (-w, -h)]
def haversine(lat1, lng1, lat2, lng2):
lng1, lat1, lng2, lat2 = map(math.radians, [lng1, lat1, lng2, lat2])
dlng = lng2 - lng1
dlat = lat2 - lat1
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlng/2)**2
return math.asin(math.sqrt(a)) * 2 * EARTH_RADIUS_KM
class LambertAzimuthalEqualAreaProjection(object):
def __init__(self, lat, lng):
self.lat = lat
self.lng = lng
self.angle = math.radians(29)
self.scale = 1
self.scale = self.kilometer_scale()
def project(self, lng, lat):
lng, lat = math.radians(lng), math.radians(lat)
clng, clat = math.radians(self.lng), math.radians(self.lat)
k = math.sqrt(2 / (1 + math.sin(clat)*math.sin(lat) + math.cos(clat)*math.cos(lat)*math.cos(lng-clng)))
x = k * math.cos(lat) * math.sin(lng-clng)
y = k * (math.cos(clat)*math.sin(lat) - math.sin(clat)*math.cos(lat)*math.cos(lng-clng))
rx = x * math.cos(self.angle) - y * math.sin(self.angle)
ry = y * math.cos(self.angle) + x * math.sin(self.angle)
x = rx
y = ry
s = self.scale
return (x * s, -y * s)
def kilometer_scale(self):
e = 1e-3
lat, lng = self.lat, self.lng
km_per_lat = haversine(lat - e, lng, lat + e, lng) / (2 * e)
km_per_lng = haversine(lat, lng - e, lat, lng + e) / (2 * e)
x1, y1 = self.project(lng - 1 / km_per_lng, lat - 1 / km_per_lat)
x2, y2 = self.project(lng + 1 / km_per_lng, lat + 1 / km_per_lat)
sx = 2 / (x2 - x1)
sy = 2 / (y1 - y2)
return (sx + sy) / 2
def transform(self, g):
result = ops.transform(self.project, g)
result.tags = g.tags
return result
def circle(cx, cy, r, n, revs=5):
points = []
n *= revs
for i in range(n + 1):
a = revs * math.pi * i / n
x = cx + math.cos(a) * r
y = cy + math.sin(a) * r
points.append((x, y))
return points
def main():
# parse osm file into shapely geometries
geometries = osm2shapely.parse(sys.argv[1])
# setup map projection
projection = LambertAzimuthalEqualAreaProjection(LAT, LNG)
geometries = [projection.transform(g) for g in geometries]
# determine width and height of map
w = MAP_WIDTH_KM
if LANDSCAPE:
h = float(w) * PAGE_HEIGHT_IN / PAGE_WIDTH_IN
else:
h = float(w) * PAGE_WIDTH_IN / PAGE_HEIGHT_IN
# process geometries
gs = []
roads = []
for g in geometries:
highway = g.tags.get('highway')
if highway and highway in WEIGHTS:
weight = WEIGHTS[highway]
if weight:
g = g.buffer(LANE_WIDTH_M / 1000.0 * weight)
g = crop(g, w * 1.1, h * 1.1)
roads.append(g)
# elif 'natural' in g.tags:
# gs.append(g)
elif 'building' in g.tags:
gs.append(g)
print 'crop'
gs = [crop(g, w * 1.1, h * 1.1) for g in gs]
roads = [crop(g, w * 1.1, h * 1.1) for g in roads]
# gs = []
# for key, ways in handler.ways.items():
# if key not in WEIGHTS:
# print 'skip', key, len(ways)
# continue
# print 'layer', key, len(ways)
# ggs = []
# for way in ways:
# coords = [projection.project(*handler.coords[x]) for x in way]
# if key == 'natural' and coords[0] == coords[-1]:
# ggs.append(geometry.Polygon(coords))
# else:
# ggs.append(geometry.LineString(coords))
# # g = paths_to_shapely(paths)
# g = geometry.collection.GeometryCollection(ggs)
# if key == 'railway':
# paths = shapely_to_paths(g)
# g = paths_to_shapely(paths)
# points = follow(g, 20 / 1000.0)
# s = 4 / 1000.0
# for x, y, a in points:
# x1 = x + math.cos(a + math.pi / 2) * s
# y1 = y + math.sin(a + math.pi / 2) * s
# x2 = x + math.cos(a - math.pi / 2) * s
# y2 = y + math.sin(a - math.pi / 2) * s
# paths.append([(x1, y1), (x2, y2)])
# g = paths_to_shapely(paths)
# if key == 'natural':
# gs.append(crop(paths_to_shapely(shapely_to_paths(g)), w * 1.1, h * 1.1))
# paths = hatch(g, 45, 10 / 1000.0)
# g = paths_to_shapely(paths)
# weight = WEIGHTS[key]
# if weight:
# g = g.buffer(LANE_WIDTH_M / 1000.0 * weight)
# g = crop(g, w * 1.1, h * 1.1)
# gs.append(g)
print 'union'
roads = ops.cascaded_union(roads)
all_roads = []
while not roads.is_empty:
all_roads.append(roads)
roads = roads.buffer(-LANE_WIDTH_M / 1000.0 / 3)
g = geometry.collection.GeometryCollection(gs + all_roads)
g = paths_to_shapely(shapely_to_paths(g))
print 'crop'
g = crop(g, w, h)
paths = shapely_to_paths(g)
# dot at origin
# s = 3 / 1000.0 / 3
# for i in range(4):
# paths.append(circle(0, 0, i * s, 360))
# border around map
s = 6 / 1000.0 / 2
for m in range(1):
paths.append(box(w - s * m, h - s * m))
print 'axi'
d = axi.Drawing(paths)
d = d.rotate_and_scale_to_fit(PAGE_WIDTH_IN, PAGE_HEIGHT_IN, step=90)
d = d.sort_paths().join_paths(0.002).simplify_paths(0.002)
im = d.render()
im.write_to_png('out.png')
axi.draw(d)
if __name__ == '__main__':
main()