TileMapBase
TileMapBase copied to clipboard
Is there a high level API?
Thank you for the project. I wonder if there is a high-level API for plotting that abstracts (automates) the choice of extent and projection?
When plotting on a map, one needs to
- Pick, calculate the extent (bounding box) of the map.
- Make plotter with the extent and choice of tile.
- Project lat-lon data into the scale of the map. I am referring to the example notebook (https://github.com/MatthewDaws/TileMapBase/blob/master/notebooks/Example.ipynb).
I think it would be convenient if there is an API that automates these configuration/preparation steps. Is there one, or if not, is there a plan?
Hi Matthew and kota7, I use tilemapbase like this: (The example code creates a Tkinter frame containing a map. I use this for a database project)
""" tilemapbase display simple map"""
# https://github.com/MatthewDaws/TileMapBase
"""
22.6.2019
There was a problem loading OSM maps
https://help.openstreetmap.org/questions/24740/why-am-i-all-of-a-sudden-getting-error-403
Workaround: use OSM_Humanitarian or Stamen
tile source can be set (when not set, default set in edit area is used
12.3.2019
It seems to be better to set the pixelwidth resolution than the zoomfactor
If the zoomfactor is used, the image has less details when zooming in
If pixelwidth is used the zoomfactor is automatically changed when zooming
11.3.2019
some experiments:
zoom with mouse -> ?
TODO: zoom with constant pixelwidth makes more sense!
Done: zoom with mouse wheel
Todo: move with mouse
problem: slow!
"""
# imports
import tilemapbase
import matplotlib.pyplot as plt
import tkinter as tk
# Tkinter backend for matplotlib:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg ##, NavigationToolbar2TkAgg
# implement the default mpl key bindings:
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np
import time
#-------------------------------------------------------------
## EDIT AREA
# use pixelwidth or zoomlevel in plotter = tilemapbase.Plotter(extent, t, ...)
pixelwidth=600 # the higher the finer the map
zoomlevel=7
size=(200,150) # window size in mm
# coordinates
my_location = (-4.8, 38) # center location
loc2=(-4.3, 38.5)
dlonlat=(4.0, 2.0) # delta (lon, lat) around center location
# map source:
tilesdefault = 'build_OSM()'
# END EDIT AREA
#-----------------------------------------------------------
## Geographic helper functions:
def getgeowindow(location, deltalonlat):
"""input: location = (lon, lat)
deltalonlat = dlon, dlat
returns: (lon1,lon2,lat1,lat2) window"""
dlon, dlat = deltalonlat
lon, lat = location
(lon1,lon2,lat1,lat2) = (lon-dlon, lon+dlon, lat-dlat, lat+dlat)
return (lon1,lon2,lat1,lat2)
#...................................................................
def shift_geowindow(window, shift):
""" input: geowindow = (lon1, lon2, lat1, lat2)
shift = dlon, dlat
returns: new (lon1,lon2,lat1,lat2) window"""
dlon, dlat = shift
(lon1,lon2,lat1,lat2) = window
(lon1,lon2,lat1,lat2) = (lon1+dlon, lon2+dlon, lat1+dlat, lat2+dlat)
return (lon1,lon2,lat1,lat2)
#....................................................................
def move_geowindow(window, dnorth, deast):
''' relative movement
dnorth, deast -1...0...+1 (relative to window)
'''
(lon1,lon2,lat1,lat2) = window
# width + height of geowindow:
dlon = lon2 - lon1
dlat = lat2 - lat1
dlon *= deast
dlat *= dnorth
(lon1,lon2,lat1,lat2) = (lon1+dlon, lon2+dlon, lat1+dlat, lat2+dlat)
return (lon1,lon2,lat1,lat2)
#....................................................................
def zoom_geowindow(window, zfactor):
(lon1,lon2,lat1,lat2) = window
# width + height of geowindow:
dlon = lon2 - lon1
dlat = lat2 - lat1
# middle:
lonM = lon1 + dlon/2
latM = lat1 + dlat/2
# new width + height:
dlon = dlon * zfactor
dlat = dlat * zfactor
# new geowindow
(lon1,lon2,lat1,lat2) = (lonM-dlon/2, lonM+dlon/2, latM-dlat/2, latM+dlat/2)
return (lon1,lon2,lat1,lat2)
#------------------------------------------------------------
''' Class MapWindow: window to display map'''
class Mapwindow():
def __init__(self, masterframe, size, tiles=tilesdefault):
""" Create a map window, set tiles source: OSM
size = (width, height) in mm
"""
# create database (at first run)
tilemapbase.init(create=True)
# Use open street map or other tiles (problem OSM 22.6.19!)
#self.tiles = tilemapbase.tiles.OSM ## problem: access forbidden 22.6.19
#self.tiles = tilemapbase.tiles.OSM_Humanitarian ## workaround 22.6.19
s='self.tiles = tilemapbase.tiles.' + tiles
print(s)
exec(s)
#self.tiles = tilemapbase.tiles.build_OSM_Humanitarian()
#self.tiles = tilemapbase.tiles.Stamen_Terrain
# create canvas:
(w,h)=size
inchsize=(w/25.4, h/25.4)
self.figure = Figure(inchsize)
self.fig=Figure(figsize=inchsize, tight_layout=True)
self.ax=self.fig.add_subplot(111)
#self.ax.xaxis.set_visible(False)
#self.ax.yaxis.set_visible(False)
self.canvas = FigureCanvasTkAgg(self.fig, master=masterframe)
self.canvas.get_tk_widget().pack()
# event binding: must be done by mpl_connect, not by bind!
self.canvas.mpl_connect('button_press_event', self.onclick)
self.canvas.mpl_connect('scroll_event', self.onscroll)
# mouse event handling (not ready)
# zoom with mouse scroll OK
# move E, W would be simple with button 1,3
# but how would we do move NS ??
def onclick(self, event):
print(event)
def onscroll(self, event):
#print(event)
print (event.button)
if event.button == "up":
self.redraw_zoom(0.8)
else:
self.redraw_zoom(1.2)
def set_lonlat(self,geowindow, aspect=False):
"""set geographic coordinates window for the map
if aspect=True: force quadratic aspect"""
self.geowindow = geowindow
lon1,lon2,lat1,lat2 = geowindow
self.extent = tilemapbase.Extent.from_lonlat(lon1,lon2,lat1,lat2)
if aspect:
self.extent = self.extent.to_aspect(1.0)
self.geowindow = lon1,lon2,lat1,lat2
print ("GEOWINDOW: ",self.geowindow)
def drawtiles_z(self, zoomlevel):
"""Draw the map with a certain zoomlevel"""
# convert extent to a collection of tiles:
# use pixelwidth or zoomlevel in plotter = tilemapbase.Plotter(extent, t, ...)
##plotter = tilemapbase.Plotter(extent, t, width=pixelwidth)
self.plotter = tilemapbase.Plotter(self.extent, self.tiles, zoom=zoomlevel)
# assemble tiles to 1 image (using PIL) and plot them with matplotlib:
self.plotter.plot(self.ax, self.tiles)
self.zoomlevel = self.plotter.zoom
def drawtiles(self, pixelwidth):
"""Draw the map with a certain pixelwidth
For varying zoom in / out this may be better than to use a fixed zoomlevel
The zoom level is automatically determined and found in self.zoomlevel"""
self.pixelwidth = pixelwidth
# convert extent to a collection of tiles:
# use pixelwidth or zoomlevel in plotter = tilemapbase.Plotter(extent, t, ...)
self.plotter = tilemapbase.Plotter(self.extent, self.tiles, width=pixelwidth)
self.zoomlevel=self.plotter.zoom
print("actual zoom level: ", self.plotter.zoom)
# assemble tiles to 1 image (using PIL) and plot them with matplotlib:
self.plotter.plot(self.ax, self.tiles)
def redraw(self, clearmarks=False):
"""Redraw the map
clearmarks = True -> erase marks before redrawing
clearmarks = False -> leave marks"""
if clearmarks==True:
self.ax.cla()
self.drawtiles(self.pixelwidth)
self.canvas.draw()
def redraw_shift(self, dlonlat, clearmarks=False):
""" Shift map about dlonlat = (dlon, dlat) in degrees"""
if clearmarks==True:
self.ax.cla()
gw= shift_geowindow(self.geowindow, dlonlat)
self.set_lonlat(gw)
self.drawtiles(self.pixelwidth)
self.canvas.draw()
def redraw_move(self, dnorth, deast, clearmarks=False):
""" Relative shift map about dnorth, deast -1....+1 (relative to window) """
if clearmarks==True:
self.ax.cla()
gw= move_geowindow(self.geowindow, dnorth, deast)
self.set_lonlat(gw)
self.drawtiles(self.pixelwidth)
self.canvas.draw()
def redraw_zoom(self, zoomfactor, clearmarks=False):
''' narrow or widen the extent of the map window by zoomfactor'''
# eventually clear old marks (or not)
if clearmarks==True:
self.ax.cla()
gw= zoom_geowindow(self.geowindow, zoomfactor)
self.set_lonlat(gw)
self.drawtiles(pixelwidth)
self.canvas.draw()
def mark(self, location, color="red", marker=".", linewidth=10, clearold=False):
"""Mark location on the map
location = (longitude, latitude)"""
# eventually clear old marks (or not)
if clearold:
self.redraw(clearmarks=True)
# mark on map
x, y = tilemapbase.project(*location)
self.ax.scatter(x,y, marker=marker, color=color, linewidth=linewidth)
self.canvas.draw()
# remember last location
self.lastlocation = location
#--------------------------------------------------
class MapFrame():
def __init__(self, masterframe, size, pixelwidth, geowindow, tiles = tilesdefault):
""" Create a map window, set tiles source: OSM
size = (width, height) in mm
with buttons to zoom + move
"""
myframe=tk.Frame(master=masterframe)
self.map = Mapwindow(myframe,size, tiles)
#pw.set_lonlat(lon1,lon2,lat1,lat2, aspect = True)
self.map.set_lonlat(geowindow)
self.map.drawtiles(pixelwidth)
self.maxpixwidth = 10000
self.minpixwidth=100
'''
#pw.drawtiles(zoomlevel)
pw.mark(my_location)
pw.mark(loc2, color="blue", marker=".")
'''
but1=tk.Button(root, text="Resolution +", command=self.resolution_finer)
but2=tk.Button(root, text="Resolution -", command=self.resolution_lesser)
#but3=tk.Button(root, text="Jump location", command=self.jump_location)
but4=tk.Button(root, text="Shift N", command=self.shift_N)
but5=tk.Button(root, text="Shift S", command=self.shift_S)
but6=tk.Button(root, text="Shift E", command=self.shift_E)
but7=tk.Button(root, text="Shift W", command=self.shift_W)
but8=tk.Button(root, text="Zoom In", command=self.zoom_in)
but9=tk.Button(root, text="Zoom Out", command=self.zoom_out)
myframe.pack()
but1.pack(side=tk.LEFT)
but2.pack(side=tk.LEFT)
##but3.pack(side=tk.LEFT)
but4.pack(side=tk.LEFT)
but5.pack(side=tk.LEFT)
but6.pack(side=tk.LEFT)
but7.pack(side=tk.LEFT)
but8.pack(side=tk.LEFT)
but9.pack(side=tk.LEFT)
def resolution_finer(self):
""" Increases pixel resolution """
if self.map.pixelwidth < self.maxpixwidth:
self.map.pixelwidth *=2
self.map.redraw()
print ("Actual pixel resolution: ", self.map.pixelwidth)
print ("Actual zoom level: ", self.map.zoomlevel)
def resolution_lesser(self):
""" Decreases zoom level by 1"""
if self.map.pixelwidth > self.minpixwidth:
self.map.pixelwidth *=0.5
self.map.redraw()
print ("Actual pixel resolution: ", self.map.pixelwidth)
print ("Actual zoom level: ", self.map.zoomlevel)
'''
def jump_location(self):
""" Deplaces marked location to the east"""
x,y=pw.lastlocation
x+=0.2
pw.mark((x,y), clearold=True)
#pw.mark((x,y))
'''
def shift_N(self):
self.map.redraw_move(0.1, 0)
def shift_S(self):
self.map.redraw_move(-0.1, 0)
def shift_E(self):
self.map.redraw_move(0, 0.1)
def shift_W(self):
self.map.redraw_move(0, -0.1)
def zoom_in(self):
self.map.redraw_zoom(0.8)
def zoom_out(self):
self.map.redraw_zoom(1.2)
#----------------------------------------------------------------
if __name__ == '__main__':
geowindow = getgeowindow(my_location, dlonlat)
print(geowindow)
root=tk.Tk()
#mw= MapFrame(root, size, pixelwidth, geowindow, tiles = 'build_OSM()')
# ALTERNATIVES:
# Attention: OSM and OSM_Humanitarian need functions!
mw= MapFrame(root, size, pixelwidth, geowindow, tiles = 'build_OSM_Humanitarian()')
#mw= MapFrame(root, size, pixelwidth, geowindow, tiles='Stamen_Toner')
#mw= MapFrame(root, size, pixelwidth, geowindow, tiles='Stamen_Toner_Hybrid')
#mw= MapFrame(root, size, pixelwidth, geowindow, tiles='Stamen_Terrain')
#mw= MapFrame(root, size, pixelwidth, geowindow, tiles='Stamen_Watercolour')
##mw= MapFrame(root, size, pixelwidth, geowindow, tiles='Carto_Dark')
#mw= MapFrame(root, size, pixelwidth, geowindow, tiles='Carto_Light')
#mw= MapFrame(root, size, pixelwidth, geowindow) # default is used
root.mainloop()