improved algorithm
This commit is contained in:
parent
a4ced15215
commit
d3692ff587
2 changed files with 359 additions and 340 deletions
34
README.md
34
README.md
|
@ -14,3 +14,37 @@ which will individually be transformed into rectangles which are also plotted.
|
||||||
On Debian systems you need the following packages:
|
On Debian systems you need the following packages:
|
||||||
|
|
||||||
apt-get install python python-pil python-scipy python-tk python-matplotlib python-numpy
|
apt-get install python python-pil python-scipy python-tk python-matplotlib python-numpy
|
||||||
|
|
||||||
|
|
||||||
|
how to setup postgresql+postgis locally without root
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
/usr/lib/postgresql/14/bin/initdb -D /tmp/postgres
|
||||||
|
/usr/lib/postgresql/14/bin/postgres --port=5433 --unix_socket_directories=/tmp/postgres -D /tmp/postgres &
|
||||||
|
/usr/lib/postgresql/14/bin/createdb --port=5433 --host=/tmp/postgres gis
|
||||||
|
/usr/lib/postgresql/14/bin/psql --port=5433 --host=/tmp/postgres gis -c 'CREATE EXTENSION postgis;' -c 'CREATE EXTENSION hstore;'
|
||||||
|
|
||||||
|
osm2pgsql --port=5433 --host=/tmp/postgres -d gis --create --slim -G --hstore --tag-transform-script /tmp/openstreetmap-carto/openstreetmap-carto.lua -S /tmp/openstreetmap-carto/openstreetmap-carto.style ~/Downloads/map.xml
|
||||||
|
/usr/lib/postgresql/14/bin/psql --port=5433 --host=/tmp/postgres gis -f /tmp/openstreetmap-carto/indexes.sql
|
||||||
|
|
||||||
|
openstreetmap-carto:
|
||||||
|
scripts/get-external-data.py --port=5433 --host=/tmp/postgres
|
||||||
|
|
||||||
|
diff --git a/project.mml b/project.mml
|
||||||
|
index 7fb3d47..d8014f8 100644
|
||||||
|
--- a/project.mml
|
||||||
|
+++ b/project.mml
|
||||||
|
@@ -27,6 +27,8 @@ _parts:
|
||||||
|
osm2pgsql: &osm2pgsql
|
||||||
|
type: "postgis"
|
||||||
|
dbname: "gis"
|
||||||
|
+ port: "5433"
|
||||||
|
+ host: "/tmp/postgres"
|
||||||
|
key_field: ""
|
||||||
|
geometry_field: "way"
|
||||||
|
extent: "-20037508,-20037508,20037508,20037508"
|
||||||
|
|
||||||
|
|
||||||
|
# the database connection settings are part of the style xml!
|
||||||
|
carto project.mml > mapnik.xml
|
||||||
|
nik4 --url https://www.openstreetmap.org/\#map\=12/49.7731/9.6726 /tmp/openstreetmap-carto/mapnik.xml screenshot.svg
|
||||||
|
|
665
mapbender.py
665
mapbender.py
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Copyright (C) 2014 - 2021 Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
|
# Copyright (C) 2014 - 2021 Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
|
||||||
#
|
#
|
||||||
|
@ -15,32 +15,47 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
import math
|
import math
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
import numpy as np
|
import numpy
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
from scipy import interpolate
|
from scipy import interpolate
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
from matplotlib.patches import Polygon
|
from PIL import Image, ImageDraw
|
||||||
from matplotlib.collections import PatchCollection
|
import urllib.request
|
||||||
import matplotlib
|
import matplotlib.path
|
||||||
from PIL import Image
|
import matplotlib.transforms
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
TILESIZE = 256
|
||||||
|
EARTHRADIUS = 6378137
|
||||||
|
|
||||||
|
|
||||||
def y2lat(a):
|
def haversine(lon1, lat1, lon2, lat2):
|
||||||
|
lon1 = math.radians(lon1)
|
||||||
|
lat1 = math.radians(lat1)
|
||||||
|
lon2 = math.radians(lon2)
|
||||||
|
lat2 = math.radians(lat2)
|
||||||
|
dlon = lon2 - lon1
|
||||||
|
dlat = lat2 - lat1
|
||||||
|
a = (
|
||||||
|
math.sin(dlat / 2) ** 2
|
||||||
|
+ math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
|
||||||
|
)
|
||||||
|
return EARTHRADIUS * 2 * math.asin(math.sqrt(a))
|
||||||
|
|
||||||
|
|
||||||
|
def lat2y(a, zoom):
|
||||||
return (
|
return (
|
||||||
180.0
|
(1.0 - math.asinh(math.tan(math.radians(a))) / math.pi)
|
||||||
/ math.pi
|
/ 2.0
|
||||||
* (2.0 * math.atan(math.exp(a * math.pi / 180.0)) - math.pi / 2.0)
|
* 2 ** zoom
|
||||||
|
* TILESIZE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def lat2y(a):
|
def lon2x(a, zoom):
|
||||||
return (
|
return (a + 180.0) / 360.0 * (2 ** zoom * TILESIZE)
|
||||||
180.0
|
|
||||||
/ math.pi
|
|
||||||
* math.log(math.tan(math.pi / 4.0 + a * (math.pi / 180.0) / 2.0))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def pairwise(iterable):
|
def pairwise(iterable):
|
||||||
|
@ -59,220 +74,150 @@ def triplewise(iterable):
|
||||||
return zip(a, b, c)
|
return zip(a, b, c)
|
||||||
|
|
||||||
|
|
||||||
# using barycentric coordinates
|
def intersects(p0, p1, p2, p3):
|
||||||
def ptInTriangle(p, p0, p1, p2):
|
s10_x = p1[0] - p0[0]
|
||||||
A = 0.5 * (
|
s10_y = p1[1] - p0[1]
|
||||||
-p1[1] * p2[0]
|
s32_x = p3[0] - p2[0]
|
||||||
+ p0[1] * (-p1[0] + p2[0])
|
s32_y = p3[1] - p2[1]
|
||||||
+ p0[0] * (p1[1] - p2[1])
|
|
||||||
+ p1[0] * p2[1]
|
denom = s10_x * s32_y - s32_x * s10_y
|
||||||
)
|
|
||||||
sign = -1 if A < 0 else 1
|
if denom == 0:
|
||||||
s = (
|
return False # collinear
|
||||||
p0[1] * p2[0] - p0[0] * p2[1] + (p2[1] - p0[1]) * p[0] + (p0[0] - p2[0]) * p[1]
|
|
||||||
) * sign
|
denom_is_positive = denom > 0
|
||||||
t = (
|
|
||||||
p0[0] * p1[1] - p0[1] * p1[0] + (p0[1] - p1[1]) * p[0] + (p1[0] - p0[0]) * p[1]
|
s02_x = p0[0] - p2[0]
|
||||||
) * sign
|
s02_y = p0[1] - p2[1]
|
||||||
return s >= 0 and t >= 0 and (s + t) <= 2 * A * sign
|
|
||||||
|
s_numer = s10_x * s02_y - s10_y * s02_x
|
||||||
|
|
||||||
|
if (s_numer < 0) == denom_is_positive:
|
||||||
|
return False # no collision
|
||||||
|
|
||||||
|
t_numer = s32_x * s02_y - s32_y * s02_x
|
||||||
|
|
||||||
|
if (t_numer < 0) == denom_is_positive:
|
||||||
|
return False # no collision
|
||||||
|
|
||||||
|
if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive:
|
||||||
|
return False # no collision
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def getxing(p0, p1, p2, p3):
|
def main(path, width, subdiv, zoom):
|
||||||
ux = p1[0] - p0[0]
|
|
||||||
uy = p1[1] - p0[1]
|
|
||||||
vx = p2[0] - p3[0]
|
|
||||||
vy = p2[1] - p3[1]
|
|
||||||
# get multiplicity of u at which u meets v
|
|
||||||
a = vy * ux - vx * uy
|
|
||||||
if a == 0:
|
|
||||||
# lines are parallel and never meet
|
|
||||||
return None
|
|
||||||
s = (vy * (p3[0] - p0[0]) + vx * (p0[1] - p3[1])) / a
|
|
||||||
if 0.0 < s < 1.0:
|
|
||||||
return (p0[0] + s * ux, p0[1] + s * uy)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# the line p0-p1 is the upper normal to the path
|
|
||||||
# the line p2-p3 is the lower normal to the path
|
|
||||||
#
|
|
||||||
# | | |
|
|
||||||
# p0--------|--------p1
|
|
||||||
# | | |
|
|
||||||
# | | |
|
|
||||||
# p3--------|--------p2
|
|
||||||
# | | |
|
|
||||||
def ptInQuadrilateral(p, p0, p1, p2, p3):
|
|
||||||
# it might be that the two normals cross at some point
|
|
||||||
# in that case the two triangles are created differently
|
|
||||||
cross = getxing(p0, p1, p2, p3)
|
|
||||||
if cross:
|
|
||||||
return ptInTriangle(p, p0, cross, p3) or ptInTriangle(p, p2, cross, p1)
|
|
||||||
else:
|
|
||||||
return ptInTriangle(p, p0, p1, p2) or ptInTriangle(p, p2, p3, p0)
|
|
||||||
|
|
||||||
|
|
||||||
def get_st(Ax, Ay, Bx, By, Cx, Cy, Dx, Dy, Xx, Xy):
|
|
||||||
d = Bx - Ax - Cx + Dx
|
|
||||||
e = By - Ay - Cy + Dy
|
|
||||||
l = Dx - Ax
|
|
||||||
g = Dy - Ay
|
|
||||||
h = Cx - Dx
|
|
||||||
m = Cy - Dy
|
|
||||||
i = Xx - Dx
|
|
||||||
j = Xy - Dy
|
|
||||||
n = g * h - m * l
|
|
||||||
# calculation for s
|
|
||||||
a1 = m * d - h * e
|
|
||||||
b1 = n - j * d + i * e
|
|
||||||
c1 = l * j - g * i
|
|
||||||
# calculation for t
|
|
||||||
a2 = g * d - l * e
|
|
||||||
b2 = n + j * d - i * e
|
|
||||||
c2 = h * j - m * i
|
|
||||||
s = []
|
|
||||||
if a1 == 0:
|
|
||||||
s.append(-c1 / b1)
|
|
||||||
else:
|
|
||||||
r1 = b1 * b1 - 4 * a1 * c1
|
|
||||||
if r1 >= 0:
|
|
||||||
r11 = (-b1 + sqrt(r1)) / (2 * a1)
|
|
||||||
if -0.0000000001 <= r11 <= 1.0000000001:
|
|
||||||
s.append(r11)
|
|
||||||
r12 = (-b1 - sqrt(r1)) / (2 * a1)
|
|
||||||
if -0.0000000001 <= r12 <= 1.0000000001:
|
|
||||||
s.append(r12)
|
|
||||||
t = []
|
|
||||||
if a2 == 0:
|
|
||||||
t.append(-c2 / b2)
|
|
||||||
else:
|
|
||||||
r2 = b2 * b2 - 4 * a2 * c2
|
|
||||||
if r2 >= 0:
|
|
||||||
r21 = (-b2 + sqrt(r2)) / (2 * a2)
|
|
||||||
if -0.0000000001 <= r21 <= 1.0000000001:
|
|
||||||
t.append(r21)
|
|
||||||
r22 = (-b2 - sqrt(r2)) / (2 * a2)
|
|
||||||
if -0.0000000001 <= r22 <= 1.0000000001:
|
|
||||||
t.append(r22)
|
|
||||||
if not s or not t:
|
|
||||||
return [], []
|
|
||||||
if len(s) == 1 and len(t) == 2:
|
|
||||||
s = [s[0], s[0]]
|
|
||||||
if len(s) == 2 and len(t) == 1:
|
|
||||||
t = [t[0], t[0]]
|
|
||||||
return s, t
|
|
||||||
|
|
||||||
|
|
||||||
def main(x, y, width, smoothing, subdiv):
|
|
||||||
halfwidth = width / 2.0
|
halfwidth = width / 2.0
|
||||||
tck, u = interpolate.splprep([x, y], s=smoothing)
|
found_smoothing = False
|
||||||
unew = np.linspace(0, 1.0, subdiv + 1)
|
for smoothing in [2 ** i for i in range(30)]:
|
||||||
out = interpolate.splev(unew, tck)
|
tck, u = interpolate.splprep(list(zip(*path)), s=smoothing)
|
||||||
heights = []
|
unew = numpy.linspace(0, 1.0, subdiv + 1)
|
||||||
offs = []
|
out = interpolate.splev(unew, tck)
|
||||||
height = 0.0
|
# prepend and append a segment
|
||||||
for (ax, ay), (bx, by) in pairwise(list(zip(*out))):
|
out = (
|
||||||
s = ax - bx
|
[2 * out[0][0] - out[0][1]] + list(out[0]) + [2 * out[0][-1] - out[0][-2]],
|
||||||
t = ay - by
|
[2 * out[1][0] - out[1][1]] + list(out[1]) + [2 * out[1][-1] - out[1][-2]],
|
||||||
l = sqrt(s * s + t * t)
|
)
|
||||||
offs.append(height)
|
heights = []
|
||||||
height += l
|
offs = []
|
||||||
heights.append(l)
|
height = 0.0
|
||||||
# the border of the first segment is just perpendicular to the path
|
for (ax, ay), (bx, by) in pairwise(zip(*out)):
|
||||||
cx = -out[1][1] + out[1][0]
|
s = ax - bx
|
||||||
cy = out[0][1] - out[0][0]
|
t = ay - by
|
||||||
cl = sqrt(cx * cx + cy * cy) / halfwidth
|
l = sqrt(s * s + t * t)
|
||||||
dx = out[1][1] - out[1][0]
|
offs.append(height)
|
||||||
dy = -out[0][1] + out[0][0]
|
height += l
|
||||||
dl = sqrt(dx * dx + dy * dy) / halfwidth
|
heights.append(l)
|
||||||
px = [out[0][0] + cx / cl]
|
# the border of the first segment is just perpendicular to the path
|
||||||
py = [out[1][0] + cy / cl]
|
cx = -out[1][1] + out[1][0]
|
||||||
qx = [out[0][0] + dx / dl]
|
cy = out[0][1] - out[0][0]
|
||||||
qy = [out[1][0] + dy / dl]
|
|
||||||
for (ubx, uby), (ux, uy), (uax, uay) in triplewise(list(zip(*out))):
|
|
||||||
# get adjacent line segment vectors
|
|
||||||
ax = ux - ubx
|
|
||||||
ay = uy - uby
|
|
||||||
bx = uax - ux
|
|
||||||
by = uay - uy
|
|
||||||
# normalize length
|
|
||||||
al = sqrt(ax * ax + ay * ay)
|
|
||||||
bl = sqrt(bx * bx + by * by)
|
|
||||||
ax = ax / al
|
|
||||||
ay = ay / al
|
|
||||||
bx = bx / bl
|
|
||||||
by = by / bl
|
|
||||||
# get vector perpendicular to sum
|
|
||||||
cx = -ay - by
|
|
||||||
cy = ax + bx
|
|
||||||
cl = sqrt(cx * cx + cy * cy) / halfwidth
|
cl = sqrt(cx * cx + cy * cy) / halfwidth
|
||||||
px.append(ux + cx / cl)
|
dx = out[1][1] - out[1][0]
|
||||||
py.append(uy + cy / cl)
|
dy = -out[0][1] + out[0][0]
|
||||||
# and in the other direction
|
|
||||||
dx = ay + by
|
|
||||||
dy = -ax - bx
|
|
||||||
dl = sqrt(dx * dx + dy * dy) / halfwidth
|
dl = sqrt(dx * dx + dy * dy) / halfwidth
|
||||||
qx.append(ux + dx / dl)
|
px = [out[0][0] + cx / cl]
|
||||||
qy.append(uy + dy / dl)
|
py = [out[1][0] + cy / cl]
|
||||||
# the border of the last segment is just perpendicular to the path
|
qx = [out[0][0] + dx / dl]
|
||||||
cx = -out[1][-1] + out[1][-2]
|
qy = [out[1][0] + dy / dl]
|
||||||
cy = out[0][-1] - out[0][-2]
|
for (ubx, uby), (ux, uy), (uax, uay) in triplewise(zip(*out)):
|
||||||
cl = sqrt(cx * cx + cy * cy) / halfwidth
|
# get adjacent line segment vectors
|
||||||
dx = out[1][-1] - out[1][-2]
|
ax = ux - ubx
|
||||||
dy = -out[0][-1] + out[0][-2]
|
ay = uy - uby
|
||||||
dl = sqrt(dx * dx + dy * dy) / halfwidth
|
bx = uax - ux
|
||||||
px.append(out[0][-1] + cx / cl)
|
by = uay - uy
|
||||||
py.append(out[1][-1] + cy / cl)
|
# normalize length
|
||||||
qx.append(out[0][-1] + dx / dl)
|
al = sqrt(ax * ax + ay * ay)
|
||||||
qy.append(out[1][-1] + dy / dl)
|
bl = sqrt(bx * bx + by * by)
|
||||||
quads = []
|
ax = ax / al
|
||||||
patches = []
|
ay = ay / al
|
||||||
for (p3x, p3y, p2x, p2y), (p0x, p0y, p1x, p1y) in pairwise(
|
bx = bx / bl
|
||||||
list(zip(px, py, qx, qy))
|
by = by / bl
|
||||||
):
|
# get vector perpendicular to sum
|
||||||
quads.append(((p0x, p0y), (p1x, p1y), (p2x, p2y), (p3x, p3y)))
|
cx = -ay - by
|
||||||
polygon = Polygon(((p0x, p0y), (p1x, p1y), (p2x, p2y), (p3x, p3y)), True)
|
cy = ax + bx
|
||||||
patches.append(polygon)
|
cl = sqrt(cx * cx + cy * cy) / halfwidth
|
||||||
containingquad = []
|
px.append(ux + cx / cl)
|
||||||
for pt in zip(x, y):
|
py.append(uy + cy / cl)
|
||||||
# for each point, find the quadrilateral that contains it
|
# and in the other direction
|
||||||
found = []
|
dx = ay + by
|
||||||
for i, (p0, p1, p2, p3) in enumerate(quads):
|
dy = -ax - bx
|
||||||
if ptInQuadrilateral(pt, p0, p1, p2, p3):
|
dl = sqrt(dx * dx + dy * dy) / halfwidth
|
||||||
found.append(i)
|
qx.append(ux + dx / dl)
|
||||||
if found:
|
qy.append(uy + dy / dl)
|
||||||
if len(found) > 1:
|
# the border of the last segment is just perpendicular to the path
|
||||||
print("point found in two quads")
|
cx = -out[1][-1] + out[1][-2]
|
||||||
return None
|
cy = out[0][-1] - out[0][-2]
|
||||||
containingquad.append(found[0])
|
cl = sqrt(cx * cx + cy * cy) / halfwidth
|
||||||
else:
|
dx = out[1][-1] - out[1][-2]
|
||||||
containingquad.append(None)
|
dy = -out[0][-1] + out[0][-2]
|
||||||
# check if the only points for which no quad could be found are in the
|
dl = sqrt(dx * dx + dy * dy) / halfwidth
|
||||||
# beginning or in the end
|
px.append(out[0][-1] + cx / cl)
|
||||||
# find the first missing ones:
|
py.append(out[1][-1] + cy / cl)
|
||||||
for i, q in enumerate(containingquad):
|
qx.append(out[0][-1] + dx / dl)
|
||||||
if q != None:
|
qy.append(out[1][-1] + dy / dl)
|
||||||
|
quads = []
|
||||||
|
for (p2x, p2y, p1x, p1y), (p3x, p3y, p0x, p0y) in pairwise(zip(px, py, qx, qy)):
|
||||||
|
quads.append(((p0x, p0y), (p1x, p1y), (p2x, p2y), (p3x, p3y)))
|
||||||
|
# check for convex quads (sides intersect)
|
||||||
|
have_convex = False
|
||||||
|
for (p0, p1, p2, p3) in quads:
|
||||||
|
if intersects(p0, p3, p1, p2):
|
||||||
|
have_convex = True
|
||||||
|
break
|
||||||
|
if have_convex:
|
||||||
|
continue
|
||||||
|
## check for quads that look too much like a triangle
|
||||||
|
# have_triangle = False
|
||||||
|
# for ((p00, p01), (p10, p11), (p20, p21), (p30, p31)) in quads:
|
||||||
|
# len1 = sqrt((p10-p00)**2+(p11-p01)**2)
|
||||||
|
# len2 = sqrt((p30-p20)**2+(p31-p21)**2)
|
||||||
|
# if len1/len2 > 100:
|
||||||
|
# have_triangle = True
|
||||||
|
# break
|
||||||
|
# if len2/len1 < 1/100:
|
||||||
|
# have_triangle = True
|
||||||
|
# break
|
||||||
|
# if have_triangle:
|
||||||
|
# continue
|
||||||
|
# draw
|
||||||
|
polygon = []
|
||||||
|
for ((p00, p01), (p10, p11), (p20, p21), (p30, p31)) in quads:
|
||||||
|
polygon.append((p10, p11))
|
||||||
|
polygon.append((p00, p01))
|
||||||
|
for ((p00, p01), (p10, p11), (p20, p21), (p30, p31)) in reversed(quads):
|
||||||
|
polygon.append((p30, p31))
|
||||||
|
polygon.append((p20, p21))
|
||||||
|
polygon.append(polygon[0])
|
||||||
|
|
||||||
|
# check if path is inside polygon
|
||||||
|
if matplotlib.path.Path(polygon).contains_path(
|
||||||
|
matplotlib.path.Path(path)
|
||||||
|
):
|
||||||
|
found_smoothing = True
|
||||||
break
|
break
|
||||||
# find the last missing ones
|
if not found_smoothing:
|
||||||
for j, q in zip(range(len(containingquad) - 1, -1, -1), reversed(containingquad)):
|
print("cannot find smoothing")
|
||||||
if q != None:
|
exit(1)
|
||||||
break
|
|
||||||
# remove the first and last missing ones
|
|
||||||
if i != 0 or j != len(containingquad) - 1:
|
|
||||||
containingquad = containingquad[i : j + 1]
|
|
||||||
x = x[i : j + 1]
|
|
||||||
y = y[i : j + 1]
|
|
||||||
# check if there are any remaining missing ones:
|
|
||||||
if None in containingquad:
|
|
||||||
print("cannot find quad for point")
|
|
||||||
return None
|
|
||||||
for off, h in zip(offs, heights):
|
|
||||||
targetquad = ((0, off + h), (width, off + h), (width, off), (0, off))
|
|
||||||
patches.append(Polygon(targetquad, True))
|
|
||||||
tx = []
|
|
||||||
ty = []
|
|
||||||
assert len(containingquad) == len(x) == len(y)
|
|
||||||
assert (
|
assert (
|
||||||
len(out[0])
|
len(out[0])
|
||||||
== len(out[1])
|
== len(out[1])
|
||||||
|
@ -284,136 +229,176 @@ def main(x, y, width, smoothing, subdiv):
|
||||||
== len(heights) + 1
|
== len(heights) + 1
|
||||||
== len(offs) + 1
|
== len(offs) + 1
|
||||||
)
|
)
|
||||||
for (rx, ry), i in zip(list(zip(x, y)), containingquad):
|
|
||||||
if i == None:
|
minx = math.inf
|
||||||
continue
|
maxx = -1
|
||||||
(ax, ay), (bx, by), (cx, cy), (dx, dy) = quads[i]
|
miny = math.inf
|
||||||
s, t = get_st(ax, ay, bx, by, cx, cy, dx, dy, rx, ry)
|
maxy = -1
|
||||||
# if more than one solution, take second
|
for (xi, yi) in polygon:
|
||||||
# TODO: investigate if this is always the right solution
|
if xi < minx:
|
||||||
if len(s) != 1 or len(t) != 1:
|
minx = xi
|
||||||
s = s[1]
|
if xi > maxx:
|
||||||
t = t[1]
|
maxx = xi
|
||||||
else:
|
if yi < miny:
|
||||||
s = s[0]
|
miny = yi
|
||||||
t = t[0]
|
if yi > maxy:
|
||||||
u = s * width
|
maxy = yi
|
||||||
v = offs[i] + t * heights[i]
|
im1 = Image.new("RGB", (int(maxx - minx), int(maxy - miny)))
|
||||||
tx.append(u)
|
im2 = Image.new("RGB", (int(maxx - minx), int(maxy - miny)))
|
||||||
ty.append(v)
|
opener = urllib.request.build_opener()
|
||||||
# sx = []
|
opener.addheaders = [("User-agent", "mapbender")]
|
||||||
# sy = []
|
urllib.request.install_opener(opener)
|
||||||
# for ((x1,y1),(x2,y2)),((ax,ay),(bx,by),(cx,cy),(dx,dy)),off,h in zip(pairwise(zip(*out)),quads,offs,heights):
|
todl = []
|
||||||
# s,t = get_st(ax,ay,bx,by,cx,cy,dx,dy,x1,y1)
|
for i in range(int(minx / TILESIZE) - 1, int(maxx / TILESIZE) + 2):
|
||||||
# if len(s) != 1 or len(t) != 1:
|
for j in range(int(miny / TILESIZE) - 1, int(maxy / TILESIZE) + 2):
|
||||||
# return None
|
os.makedirs("%d/%d" % (zoom, i), exist_ok=True)
|
||||||
# u = s[0]*width
|
fname = "%d/%d/%d.png" % (zoom, i, j)
|
||||||
# v = off+t[0]*h
|
if not matplotlib.path.Path(numpy.array(polygon)).intersects_bbox(
|
||||||
# sx.append(u)
|
matplotlib.transforms.Bbox(
|
||||||
# sy.append(v)
|
[
|
||||||
# s,t = get_st(ax,ay,bx,by,cx,cy,dx,dy,x2,y2)
|
(i * TILESIZE, j * TILESIZE),
|
||||||
# if len(s) != 1 or len(t) != 1:
|
(
|
||||||
# return None
|
(i + 1) * TILESIZE,
|
||||||
# u = s[0]*width
|
(j + 1) * TILESIZE,
|
||||||
# v = off+t[0]*h
|
),
|
||||||
# sx.append(u)
|
]
|
||||||
# sy.append(v)
|
)
|
||||||
# create map with
|
):
|
||||||
# python -c 'import logging; logging.basicConfig(level=logging.DEBUG); from landez import ImageExporter; ie = ImageExporter(tiles_url="http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png"); ie.export_image(bbox=(8.0419921875,51.25160146817652,10.074462890625,54.03681240523652), zoomlevel=14, imagepath="image.png")'
|
continue
|
||||||
im = Image.open("map.png")
|
if not os.path.exists(fname):
|
||||||
bbox = [8.0419921875, 51.25160146817652, 10.074462890625, 54.03681240523652]
|
todl.append((i, j))
|
||||||
# apply mercator projection
|
for n, (i, j) in enumerate(todl):
|
||||||
bbox[1] = lat2y(bbox[1])
|
print("%d/%d" % (n, len(todl)))
|
||||||
bbox[3] = lat2y(bbox[3])
|
fname = "%d/%d/%d.png" % (zoom, i, j)
|
||||||
iw, ih = im.size
|
urllib.request.urlretrieve(
|
||||||
|
#"https://tile.openstreetmap.org/%d/%d/%d.png" % (zoom, i, j),
|
||||||
|
#https://a.tile.thunderforest.com/cycle/17/68690/44518.png?apikey=6170aad10dfd42a38d4d8c709a53
|
||||||
|
"https://tile.thunderforest.com/cycle/%d/%d/%d.png?apikey=d8f470ce7a8e4dd0acf39cc8fd3cf979" % (zoom, i, j),
|
||||||
|
#"https://tile.thunderforest.com/outdoors/%d/%d/%d.png?apikey=d8f470ce7a8e4dd0acf39cc8fd3cf979" % (zoom, i, j),
|
||||||
|
#"https://tile.thunderforest.com/landscape/%d/%d/%d.png?apikey=d8f470ce7a8e4dd0acf39cc8fd3cf979" % (zoom, i, j),
|
||||||
|
#"https://tile.thunderforest.com/atlas/%d/%d/%d.png?apikey=d8f470ce7a8e4dd0acf39cc8fd3cf979" % (zoom, i, j),
|
||||||
|
filename=fname,
|
||||||
|
)
|
||||||
|
for i in range(int(minx / TILESIZE) - 1, int(maxx / TILESIZE) + 2):
|
||||||
|
for j in range(int(miny / TILESIZE) - 1, int(maxy / TILESIZE) + 2):
|
||||||
|
if not matplotlib.path.Path(numpy.array(polygon)).intersects_bbox(
|
||||||
|
matplotlib.transforms.Bbox(
|
||||||
|
[
|
||||||
|
(i * TILESIZE, j * TILESIZE),
|
||||||
|
(
|
||||||
|
(i + 1) * TILESIZE,
|
||||||
|
(j + 1) * TILESIZE,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
fname = "%d/%d/%d.png" % (zoom, i, j)
|
||||||
|
with Image.open(fname) as tile:
|
||||||
|
im1.paste(tile, (int(i * TILESIZE - minx), int(j * TILESIZE - miny)))
|
||||||
|
im2.paste(tile, (int(i * TILESIZE - minx), int(j * TILESIZE - miny)))
|
||||||
|
draw2 = ImageDraw.Draw(im2)
|
||||||
|
draw2.line([(xi - minx, yi - miny) for xi, yi in path], fill=(255, 0, 0), width=4)
|
||||||
|
draw1 = ImageDraw.Draw(im1)
|
||||||
|
draw1.line([(xi - minx, yi - miny) for xi, yi in path], fill=(255, 0, 0), width=4)
|
||||||
|
draw1.line([(xi - minx, yi - miny) for xi, yi in zip(*out)], fill=(0, 255, 0))
|
||||||
|
for ((p00, p01), (p10, p11), (p20, p21), (p30, p31)) in quads:
|
||||||
|
draw1.polygon(
|
||||||
|
[
|
||||||
|
(p00 - minx, p01 - miny),
|
||||||
|
(p10 - minx, p11 - miny),
|
||||||
|
(p20 - minx, p21 - miny),
|
||||||
|
(p30 - minx, p31 - miny),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
draw1.polygon([(xi - minx, yi - miny) for xi, yi in polygon], outline=(0, 0, 255))
|
||||||
|
im1.save("out2.png")
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for i, (off, h, (p0, p1, p2, p3)) in enumerate(zip(offs, heights, quads)):
|
for i, (off, h, (p0, p1, p2, p3)) in enumerate(zip(offs, heights, quads)):
|
||||||
# first, account for the offset of the input image
|
data.append(
|
||||||
p0 = p0[0] - bbox[0], p0[1] - bbox[1]
|
(
|
||||||
p1 = p1[0] - bbox[0], p1[1] - bbox[1]
|
(
|
||||||
p2 = p2[0] - bbox[0], p2[1] - bbox[1]
|
0,
|
||||||
p3 = p3[0] - bbox[0], p3[1] - bbox[1]
|
int(height - offs[i] - heights[i]),
|
||||||
# PIL expects coordinates in counter clockwise order
|
int(width),
|
||||||
p1, p3 = p3, p1
|
int(height - offs[i]),
|
||||||
# x lon
|
),
|
||||||
# ----- = -----
|
(
|
||||||
# w bbox[2]-bbox[0]
|
p0[0] - minx,
|
||||||
# translate to pixel coordinates
|
p0[1] - miny,
|
||||||
p0 = (iw * p0[0]) / (bbox[2] - bbox[0]), (ih * p0[1]) / (bbox[3] - bbox[1])
|
p1[0] - minx,
|
||||||
p1 = (iw * p1[0]) / (bbox[2] - bbox[0]), (ih * p1[1]) / (bbox[3] - bbox[1])
|
p1[1] - miny,
|
||||||
p2 = (iw * p2[0]) / (bbox[2] - bbox[0]), (ih * p2[1]) / (bbox[3] - bbox[1])
|
p2[0] - minx,
|
||||||
p3 = (iw * p3[0]) / (bbox[2] - bbox[0]), (ih * p3[1]) / (bbox[3] - bbox[1])
|
p2[1] - miny,
|
||||||
# PIL starts coordinate system at the upper left corner, swap y coord
|
p3[0] - minx,
|
||||||
p0 = int(p0[0]), int(ih - p0[1])
|
p3[1] - miny,
|
||||||
p1 = int(p1[0]), int(ih - p1[1])
|
),
|
||||||
p2 = int(p2[0]), int(ih - p2[1])
|
)
|
||||||
p3 = int(p3[0]), int(ih - p3[1])
|
|
||||||
box = (
|
|
||||||
0,
|
|
||||||
int(ih * (height - off - h) / (bbox[3] - bbox[1])),
|
|
||||||
int(iw * width / (bbox[2] - bbox[0])),
|
|
||||||
int(ih * (height - off) / (bbox[3] - bbox[1])),
|
|
||||||
)
|
)
|
||||||
quad = (p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1])
|
im_out = im2.transform(
|
||||||
data.append((box, quad))
|
(int(width), int(height)),
|
||||||
im_out = im.transform(
|
|
||||||
(int(iw * width / (bbox[2] - bbox[0])), int(ih * height / (bbox[3] - bbox[1]))),
|
|
||||||
Image.MESH,
|
Image.MESH,
|
||||||
data,
|
data,
|
||||||
Image.BICUBIC,
|
Image.BICUBIC,
|
||||||
)
|
)
|
||||||
im_out.save("out.png")
|
im_out.save("out.png")
|
||||||
|
im_out.save("out.jpg", quality=95)
|
||||||
|
|
||||||
# np.random.seed(seed=0)
|
|
||||||
# colors = 100*np.random.rand(len(patches)//2)+100*np.random.rand(len(patches)//2)
|
|
||||||
# p = PatchCollection(patches, cmap=matplotlib.cm.jet, alpha=0.4)
|
|
||||||
# p.set_array(np.array(colors))
|
|
||||||
# plt.figure()
|
|
||||||
# plt.axes().set_aspect('equal')
|
|
||||||
##plt.axhspan(0, height, xmin=0, xmax=width)
|
|
||||||
# fig, ax = plt.subplots()
|
|
||||||
##ax.add_collection(p)
|
|
||||||
# ax.set_aspect('equal')
|
|
||||||
# plt.axis((0,width,0,height))
|
|
||||||
# plt.imshow(np.asarray(im_out),extent=[0,width,0,height])
|
|
||||||
# plt.imshow(np.asarray(im),extent=[bbox[0],bbox[2],bbox[1],bbox[3]])
|
|
||||||
# plt.plot(x,y,out[0],out[1],px,py,qx,qy,tx,ty)
|
|
||||||
# plt.show()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
x = []
|
|
||||||
y = []
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if len(sys.argv) != 5:
|
if len(sys.argv) != 4:
|
||||||
print("usage: %s data.csv width smoothing N" % sys.argv[0])
|
print("usage: %s data.gpx mapwidth paperwidth" % sys.argv[0])
|
||||||
print("")
|
|
||||||
print(
|
|
||||||
" data.csv whitespace delimited lon/lat pairs of points along the path"
|
|
||||||
)
|
|
||||||
print(" width width of the resulting map in degrees")
|
|
||||||
print(
|
|
||||||
" smoothing curve smoothing from 0 (exact fit) to higher values (looser fit)"
|
|
||||||
)
|
|
||||||
print(" N amount of quads to split the path into")
|
|
||||||
print("")
|
|
||||||
print(" example usage:")
|
|
||||||
print(" %s Weser-Radweg-Hauptroute.csv 0.286 6 20" % sys.argv[0])
|
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
zoom = 10
|
||||||
|
latmin = math.inf
|
||||||
|
latmax = -1
|
||||||
|
|
||||||
|
path = []
|
||||||
with open(sys.argv[1]) as f:
|
with open(sys.argv[1]) as f:
|
||||||
for l in f:
|
root = ET.parse(f)
|
||||||
a, b = l.split()
|
for trkpt in root.findall(
|
||||||
|
"./gpx:trk/gpx:trkseg/gpx:trkpt",
|
||||||
|
{"gpx": "http://www.topografix.com/GPX/1/1"},
|
||||||
|
):
|
||||||
|
lat = float(trkpt.attrib["lat"])
|
||||||
|
lon = float(trkpt.attrib["lon"])
|
||||||
|
if lat < latmin:
|
||||||
|
latmin = lat
|
||||||
|
if lat > latmax:
|
||||||
|
latmax = lat
|
||||||
# apply mercator projection
|
# apply mercator projection
|
||||||
b = lat2y(float(b))
|
path.append((lon, lat))
|
||||||
x.append(float(a))
|
|
||||||
y.append(b)
|
length = 0
|
||||||
width = float(sys.argv[2])
|
for (lon1, lat1), (lon2, lat2) in pairwise(path):
|
||||||
smoothing = float(sys.argv[3])
|
length += haversine(lon1, lat1, lon2, lat2)
|
||||||
N = int(sys.argv[4])
|
|
||||||
main(x, y, width, smoothing, N)
|
dpi = 96 # because we use bitmap tiles instead of vectors
|
||||||
# for smoothing in [1,2,4,8,12]:
|
mapwidthm = float(sys.argv[2]) # map width in m
|
||||||
# for subdiv in range(10,30):
|
paperwidthm = float(sys.argv[3]) # paper width in m
|
||||||
# if main(x,y,width,smoothing,subdiv):
|
earth = 6378137 # earth equator radius in m
|
||||||
# print width,smoothing,subdiv
|
widthpx = dpi / 0.0254 * paperwidthm
|
||||||
|
zoom = math.ceil(
|
||||||
|
math.log2(
|
||||||
|
2
|
||||||
|
* math.pi
|
||||||
|
* earth
|
||||||
|
* math.cos(math.radians((latmax + latmin) / 2))
|
||||||
|
* widthpx
|
||||||
|
/ (mapwidthm * TILESIZE)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
subdiv = math.ceil(4*length/mapwidthm)
|
||||||
|
print("zoom:", zoom)
|
||||||
|
print("length:", length)
|
||||||
|
print("subdiv:", subdiv)
|
||||||
|
|
||||||
|
path = [(lon2x(lon, zoom), lat2y(lat, zoom)) for lon, lat in path]
|
||||||
|
|
||||||
|
main(path, widthpx, subdiv, zoom)
|
||||||
|
|
Loading…
Reference in a new issue