There will be a webinar on pyGIMLi hosted by SEG on March 19, 2024 at 4 pm CET. Register for free here.

Source code for pygimli.viewer.mpl.overlayimage

# -*- coding: utf-8 -*-
"""Overlay / Underlay an image or a geo referenced map to mpl.ax."""

import os
import math

import numpy as np
import matplotlib.image as mpimg

import pygimli as pg


class OverlayImageMPL(object):
    """TODO Documentme."""

    def __init__(self, imageFileName, ax):
        """Constructor."""
        self.ax = ax
        self.imAxes = None
        self.image = mpimg.open(imageFileName)
        self.figure = self.ax.get_figure()
        self.dx = 0
        self.dy = 0

    def clear(self):
        """TODO Documentme."""
        if self.imAxes in self.figure.ax:
            self.figure.delax(self.imAxes)

    def setPosition(self, posX, posY, ax=None):
        """TODO Documentme."""
        if ax is not None:
            self.ax = ax
        self.dx = float(self.image.size[0]) / \
            self.figure.get_dpi() / self.figure.get_size_inches()[0]
        self.dy = float(self.image.size[0]) / \
            self.figure.get_dpi() / self.figure.get_size_inches()[1]

        xRange = self.ax.get_xlim()[1] - self.ax.get_xlim()[0]
        yRange = self.ax.get_ylim()[1] - self.ax.get_ylim()[0]

        x = (posX - self.ax.get_xlim()[0]) / xRange
        y = (posY - self.ax.get_ylim()[0]) / yRange

        x *= (self.ax.get_position().x1 - self.ax.get_position().x0)
        y *= (self.ax.get_position().y1 - self.ax.get_position().y0)

        # print self.imAxes
        # print self.figure.ax
        if self.imAxes not in self.figure.ax:
            if (x + self.ax.get_position().x0) > 10:
                print(("overlay size out of range",
                       (x + self.ax.get_position().x0)))
                print((posX, posY))
                print((xRange, yRange))
                print((x, y))
                print((self.ax.get_position().x0,
                       self.ax.get_position().x1))
                print((self.figure.get_size_inches()))
                print(("add ax",
                       [x + self.ax.get_position().x0 - self.dx / 6.0,
                        y + self.ax.get_position().y0, self.dx, self.dy]))
                # hackish
                return

            self.imAxes = self.figure.add_ax([
                x + self.ax.get_position().x0 - self.dx / 6.0,
                y + self.ax.get_position().y0,
                self.dx, self.dy], frameon=False, axisbg='y')
        else:
            self.imAxes.set_position([
                x + self.ax.get_position().x0 - self.dx / 6.0,
                y + self.ax.get_position().y0,
                self.dx, self.dy])

        if len(self.imAxes.get_xticks()) > 0:
            print("overlay imshow")
            self.imAxes.imshow(self.image, origin='lower')
            self.imAxes.set_xticks([])
            self.imAxes.set_yticks([])


class MapTilesCacheSingleton(object):
    __instance = None
    _tilesCache = dict()

    def __new__(cls):
        if MapTilesCacheSingleton.__instance is None:
            MapTilesCacheSingleton.__instance = object.__new__(cls)
        return MapTilesCacheSingleton.__instance

    def add(self, key, tile):
        self._tilesCache[key] = tile

    def get(self, key):
        if key in self._tilesCache:
            return self._tilesCache[key]
        return None


# We only want one instance of this global cache so its a singleton class
__MatTilesCache__ = MapTilesCacheSingleton()


[docs] def deg2MapTile(lon_deg, lat_deg, zoom): """TODO Documentme.""" lat_rad = math.radians(lat_deg) n = 2.0 ** zoom xtile = int((lon_deg + 180.0) / 360.0 * n) ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n) # correct the latitude to go from 0 (north) to 180 (south), # instead of 90(north) to -90(south) latitude = 90 - lat_deg # correct the longitude to go from 0 to 360 longitude = 180 + lon_deg # find tile size from zoom level latTileSize = 180/(pow(2, (17-zoom))) longTileSize = 360/(pow(2, (17-zoom))) # find the tile coordinates # tilex = int(longitude / longTileSize) # tiley = int(latitude / latTileSize) return (xtile, ytile)
[docs] def mapTile2deg(xtile, ytile, zoom): """Calculate the NW-corner of the square. Use the function with xtile+1 and/or ytile+1 to get the other corners. With xtile+0.5 ytile+0.5 it will return the center of the tile. """ n = 2.0 ** zoom lon_deg = xtile / n * 360.0 - 180.0 lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n))) lat_deg = math.degrees(lat_rad) return (lon_deg, lat_deg)
[docs] def cacheFileName(fullname, vendor): """Createfilename and path to cache download data.""" (dirName, fileName) = os.path.split(fullname) # os.path.joint(pg.getConfigPath(), fileName) path = os.path.join(pg.getConfigPath(), vendor, dirName) try: os.makedirs(path) except OSError: pass return os.path.join(path, fileName)
[docs] def getMapTile(xtile, ytile, zoom, vendor='OSM', verbose=False): """Get a map tile from public mapping server. Its not recommended to use the google maps tile server without the google maps api so if you use it to often your IP will be blacklisted TODO: look here for more vendors https://github.com/Leaflet/Leaflet TODO: Try http://scitools.org.uk/cartopy/docs/v0.14/index.html TODO: Try https://github.com/jwass/mplleaflet Parameters ---------- xtile : int ytile : int zoom : int vendor : str . 'OSM' or 'Open Street Map' (tile.openstreetmap.org) . 'GM' or 'Google Maps' (mt.google.com) (do not use it to much) verbose : bool [false] be verbose """ imagename = str(zoom) + '/' + str(xtile) + '/' + str(ytile) if vendor == 'OSM' or vendor == 'Open Street Map': # http://[abc].tile.openstreetmap.org serverName = 'tile.openstreetmap.org' url = 'http://a.' + serverName + '/' + imagename + '.png' imFormat = '.png' elif vendor == 'GM' or vendor == 'Google Maps': # its not recommended to use the google maps tile server without # google maps api .. if you use it to often your IP will be blacklisted # mt.google.com will balance itself serverName = 'http://mt.google.com' # nr = random.randint(1, 4) # serverName = 'http://mt' + str(nr) + '.google.com' # LAYERS: # h = roads only # m = standard roadmap # p = terrain # r = somehow altered roadmap # s = satellite only # t = terrain only # y = hybrid # ,transit url = serverName + '/vt/lyrs=m' + \ '&x=' + str(xtile) + '&y=' + str(ytile) + \ '&hl=en' + '&z=' + str(zoom) imFormat = '.png' else: raise "Vendor: " + vendor + \ " not supported (currently only OSM (Open Street Map))" filename = cacheFileName(imagename, serverName) + imFormat image = __MatTilesCache__.get(filename) if image is None: if os.path.exists(filename): if verbose: print(("Read image from disk", filename)) image = mpimg.imread(filename) image = image[:, :, 0:3] else: if verbose: print(("Get map from url maps", url)) image = mpimg.imread(url) if verbose: print(imagename) mpimg.imsave(filename, image) if imFormat == '.jpeg': image = image[::-1, ...] / 256. __MatTilesCache__.add(filename, image) else: if verbose: print(("Took image from cache", filename)) return image
# def getMapTile(...)
[docs] def underlayMap(ax, proj, vendor='OSM', zoom=-1, pixelLimit=None, verbose=False, fitMap=False): """Get a map from public mapping server and underlay it on the given ax. Parameters ---------- ax : matplotlib.ax proj : pyproy Proj Projection vendor : str . 'OSM' or 'Open Street Map' (tile.openstreetmap.org) . 'GM' or 'Google Maps' (mt.google.com) zoom : int [-1] Zoom level. If zoom is set to -1, the pixel size of the resulting image is lower than pixelLimit. pixelLimit : [int,int] verbose : bool [false] be verbose fitMap : bool The ax is resized to fit the whole map. """ if pixelLimit is None: pixelLimit = [1024, 1024] origXLimits = ax.get_xlim() origYLimits = ax.get_ylim() ul = proj(ax.get_xlim()[0], ax.get_ylim()[1], inverse=True) lr = proj(ax.get_xlim()[1], ax.get_ylim()[0], inverse=True) if zoom == -1: nXtiles = 1e99 nYtiles = 1e99 zoom = 19 while ((nYtiles * 256) > pixelLimit[0] or (nXtiles * 256) > pixelLimit[1]): zoom = zoom - 1 startTile = deg2MapTile(ul[0], ul[1], zoom) endTile = deg2MapTile(lr[0], lr[1], zoom) nXtiles = (endTile[0] - startTile[0]) + 1 nYtiles = (endTile[1] - startTile[1]) + 1 if verbose: print(("tiles: ", zoom, nYtiles, nXtiles)) if nXtiles == 1 and nYtiles == 1: break if verbose: print(("zoom set to ", zoom)) startTile = deg2MapTile(ul[0], ul[1], zoom) endTile = deg2MapTile(lr[0], lr[1], zoom) nXtiles = (endTile[0] - startTile[0]) + 1 nYtiles = (endTile[1] - startTile[1]) + 1 image = np.zeros(shape=(256 * nYtiles, 256 * nXtiles, 3)) if verbose: print(("Mapimage size:", image.shape)) for i in range(nXtiles): for j in range(nYtiles): im = getMapTile(startTile[0] + i, startTile[1] + j, zoom, vendor, verbose=verbose) image[(j * 256): ((j + 1) * 256), (i * 256): ((i + 1) * 256)] = im lonLatStart = mapTile2deg(startTile[0], startTile[1], zoom) lonLatEnd = mapTile2deg(endTile[0] + 1, endTile[1] + 1, zoom) imUL = proj(lonLatStart[0], lonLatStart[1]) imLR = proj(lonLatEnd[0], lonLatEnd[1]) extent = np.asarray([imUL[0], imLR[0], imLR[1], imUL[1]]) print(extent) gci = ax.imshow(image, extent=extent) if not fitMap: ax.set_xlim(origXLimits) ax.set_ylim(origYLimits) else: ax.set_xlim(extent[0], extent[1]) ax.set_ylim(extent[2], extent[3]) return gci
def getBKGaddress(xlim, ylim, imsize=1000, zone=32, service='dop40', usetls=False, epsg=0, uuid='', fmt='image/jpeg', layer='rgb'): """Generate address for rendering web service image from BKG. Assumes UTM in given zone. """ url = 'https://sgx.geodatenzentrum.de/wms_' + service if usetls: url = 'https://sgtls12.geodatenzentrum.de/wms_' + service # new stdarg = '?service=wms&version=1.3.0&request=GetMap&Layers=' + layer stdarg += '&STYLES=default' if epsg == 0: # epsg = 32600 + zone # WGS 84 / UTM zone 32N epsg = 25800 + zone # ETRS89 / UTM zone 32N srsstr = 'CRS=EPSG:' + str(epsg) # EPSG definition of UTM if imsize is None or imsize <= 1: imsize = int((xlim[1] - xlim[0])/0.4) + 1 # take 40cm DOP resolution print('choose image size ', imsize) box = ','.join(str(int(v)) for v in [xlim[0], ylim[0], xlim[1], ylim[1]]) ysize = int((imsize - 1.) * (ylim[1] - ylim[0]) / (xlim[1] - xlim[0])) + 1 sizestr = 'width=' + str(imsize) + '&Height=' + '%d' % ysize if uuid: url += '__' + uuid addr = url + stdarg + '&' + srsstr + \ '&' + 'bbox=' + box + '&' + sizestr + '&Format=' + fmt return addr, box
[docs] def underlayBKGMap(ax, mode='DOP', utmzone=32, epsg=0, imsize=2500, uuid='', usetls=False, origin=None, imname=None, box=None): """Underlay digital orthophoto or topographic (mode='DTK') map under axes. First accessed, the image is obtained from BKG, saved and later loaded. Parameters ---------- mode : str 'DOP' (digital orthophoto 40cm) or 'DTK' (digital topo map 1:25000) imsize : int image width in pixels (height will be automatically determined utmzone : int [32] UTM (north) zone to use epsg : int EPSG code for coordinate system uuid : str user ID for payed services usetls : bool [False] use TLS socket layer origin : (float, float) origin to add to the coordinates imname : str use predefined file instead of download using axis bounding box box : (float, float, float, float) (if imname given) specify bounding box (otherwise take from imname) """ if imname is not None: # use predefined image box = box or imname[3:-4] # bb = np.array(imname[3:-4].split(","), # dtype=float).take([0, 2, 1, 3]) else: try: import urllib.request as urllib2 except ImportError: import urllib2 ext = {'DOP': '.jpg', 'DTK': '.png', 'MAP': '.png'} # extensions for different maps wms = {'DOP': 'dop40', 'DTK': 'dtk25', 'MAP': 'basemapde'} # wms service name for maps fmt = {'DOP': 'image/jpeg', 'DTK': 'image/png', 'MAP': 'image/png'} # format lay = {'DOP': 'rgb', 'DTK': '0', 'MAP': 'de_basemapde_web_raster_farbe'} if imsize < 1: # 0, -1 or 0.4 could be reasonable parameters ax = ax.get_xlim() imsize = int((ax[1] - ax[0]) / 0.4) # use original 40cm pixel size if imsize > 5000: # limit overly sized images imsize = 2500 # default value xl = np.array(ax.get_xlim()) yl = np.array(ax.get_ylim()) if origin: xl += origin[0] yl += origin[1] ad, box = getBKGaddress(xl, yl, imsize, zone=utmzone, service=wms[mode.upper()], usetls=usetls, uuid=uuid, epsg=epsg, fmt=fmt[mode.upper()], layer=lay[mode.upper()]) imname = mode + box + ext[mode] if not os.path.isfile(imname): # not already existing pg.info('Retrieving file from geodatenzentrum.de using URL: ' + ad) req = urllib2.Request(ad) response = urllib2.urlopen(req) with open(imname, 'wb') as output: output.write(response.read()) else: pg.info('Found image file: ' + imname) im = mpimg.imread(imname) bb = [int(bi) for bi in box.split(',')] # bounding box if origin: bb[0] -= origin[0] bb[2] -= origin[0] bb[1] -= origin[1] bb[3] -= origin[1] ax.imshow(im, extent=[bb[0], bb[2], bb[1], bb[3]], interpolation='nearest')