def basemap(zoom=12): imagery = OSM() ax = plt.axes(projection=imagery.crs) ax.set_extent(LONDON) extent = ax._get_extent_geom(imagery.crs) img, extent, origin = imagery.image_for_domain(extent, zoom) plt.close(ax.figure) return {'img': img, 'extent': extent, 'origin': origin}
def main(): imagery = OSM() fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection=imagery.crs) ax.set_extent([-0.14, -0.1, 51.495, 51.515], ccrs.PlateCarree()) # Construct concentric circles and a rectangle, # suitable for a London Underground logo. theta = np.linspace(0, 2 * np.pi, 100) circle_verts = np.vstack([np.sin(theta), np.cos(theta)]).T concentric_circle = Path.make_compound_path(Path(circle_verts[::-1]), Path(circle_verts * 0.6)) rectangle = Path([[-1.1, -0.2], [1, -0.2], [1, 0.3], [-1.1, 0.3]]) # Add the imagery to the map. ax.add_image(imagery, 14) # Plot the locations twice, first with the red concentric circles, # then with the blue rectangle. xs, ys = tube_locations().T ax.plot(xs, ys, transform=ccrs.OSGB(), marker=concentric_circle, color='red', markersize=9, linestyle='') ax.plot(xs, ys, transform=ccrs.OSGB(), marker=rectangle, color='blue', markersize=11, linestyle='') ax.set_title('London underground locations') plt.show()
def create_map(long, lat, ext): # Set projection type projection = ccrs.PlateCarree() # Fetch map tiles tiles = OSM() # Create figure and ax with gridlines and formated axes fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection=projection)) gl = ax.gridlines(draw_labels=True) gl.xlabels_top = False gl.ylabels_right = False gl.xformatter = LONGITUDE_FORMATTER gl.yformatter = LATITUDE_FORMATTER # Take part of projection calculated by ext ax.set_extent(ext, projection) # Add tiles to ax with zoom 7 and sline36 interpolation ax.add_image(tiles, 7, interpolation='spline36') # Plot observing point ax.plot([long], [lat], 'bs') # Add title to plit fig.suptitle('Live Flight Tracker', fontsize=16) # Create empty scatter plot track_flights = ax.scatter([], [], marker='o', c=[], s=14, alpha=.85, edgecolors='k') return fig, ax, track_flights
def __init__(self, update_interval = 1, trajectory = False): self.update_interval = update_interval self.trajectory = trajectory imagery = OSM() self.fig = plt.figure() self.fig.subplots_adjust(left = 0, right = 1, bottom = 0, top = 1) ax = self.fig.add_subplot(1, 1, 1, projection = imagery.crs) ax.set_extent([ PosePlotter.lng_min, PosePlotter.lng_max, PosePlotter.lat_min, PosePlotter.lat_max ], ccrs.PlateCarree()) ax.add_image(imagery, 17) self.poses = dict() self.colors = PosePlotter.color_generator() plt.ion() plt.hold(True) plt.draw() plt.show()
def plotTracks(self, files, osmZoomLevel=10, padding=0.1): # ToDo: adapt OSM zoom level automatically # Make plot interactive, allow user to zoom, scroll, pan; adapt map bg accordingly # Padding: add padding on all four sides so tracks don't end at edge of map. 0.1: 10% padding on all sides. if len(files) == 0: raise ValueError("gpxTools.plotTracks: need at least one file to work with!") if padding < 0: raise ValueError("gpxTools.plotTracks: padding value is %f; needs to be non-negative."%padding) # Open with fiona: # Straightforward to generate shapes, but doesn't retain time information. trackShapes=[] # extent: bounding box for map plot: minLon, maxLon, minLat, maxLat extent=[sys.float_info.max, sys.float_info.min, sys.float_info.max, sys.float_info.min] for fn in files: tracks=fiona.open(fn, layer='tracks') for track in tracks: coords=track['geometry']['coordinates'] shp=sgeom.MultiLineString(coords) # shp.bounds has different order than extent... if shp.bounds[0]<extent[0]: extent[0] = shp.bounds[0] if shp.bounds[2] > extent[1]: extent[1]=shp.bounds[2] if shp.bounds[1] < extent[2]: extent[2] = shp.bounds[1] if shp.bounds[3] > extent[3]: extent[3] = shp.bounds[3] trackShapes.append(shp) # Add padding: lonShift=padding*(extent[1]-extent[0]) extent[0]=extent[0]-lonShift extent[1]=extent[1]+lonShift latShift=padding*(extent[3]-extent[2]) extent[2]=extent[2]-latShift extent[3]=extent[3]+latShift # Start plotting osm=OSM() # Following https://ocefpaf.github.io/python4oceanographers/blog/2015/08/03/fiona_gpx/ ax = plt.axes(projection=osm.crs) gl=ax.gridlines(draw_labels=True) gl.xlabels_top = gl.ylabels_right = False gl.xformatter = LONGITUDE_FORMATTER gl.yformatter = LATITUDE_FORMATTER ax.set_extent(extent) ax.add_image(osm,osmZoomLevel) ## Fiona for i, track in enumerate(trackShapes): ax.add_geometries(track, crs=ccrs.PlateCarree(), edgecolor=self.plotColors[i % len(self.plotColors)], linewidth=2, facecolor='none') plt.tight_layout() plt.show() return
def get_map_params(image='StamenTerrain', color_palette=None): # set color palette if color_palette: cmap = plt.get_cmap(color_palette) else: cmap = None # set background image if image == 'StamenTerrain': tiler = StamenTerrain() elif image == 'GoogleTiles': tiler = GoogleTiles() elif image == 'OSM': tiler = OSM() return cmap, tiler
def render_pyplot(df, dataset_args=None, render_args=None): from cartopy.io.img_tiles import OSM tiles = OSM() fig = plt.figure(figsize=(18, 18)) ax = plt.axes(projection=tiles.crs) for route in df['route_id'].unique(): df_route = df.loc[df['route_id'] == route] ax.plot(df_route.stop_lon.values, df_route.stop_lat.values, color=get_color_for_str(route), transform=ccrs.Geodetic(), markersize=2, marker='o') filename = 'pyplot_' + \ '_'.join([dataset_args['dataset'], dataset_args['agency'], dataset_args['route']]) plt.savefig(f'{filename}.png')
def __cartopy_geo_axis(self, zoom: int) -> (GeoAxesSubplot, dict): osm = OSM() axis = axes(projection=osm.crs) axis.set_extent(self.__density.bounds.extent, crs=PlateCarree()) axis.add_image(osm, zoom) axis.text(-0.05, 0.50, 'latitude', rotation='vertical', verticalalignment='center', transform=axis.transAxes) axis.text(0.5, -0.05, 'longitude', rotation='horizontal', horizontalalignment='center', transform=axis.transAxes) plot_params = { 'cmap': transparent_viridis(), 'transform': PlateCarree() } return axis, plot_params
def generate(self, figsize, dpi, zoom, markersize): """ This generates the basic map and draws dots on the given location. Args: figsize (tuple of int): Figure size. dpi (int): Dots per inch. zoom (int): Map zoom. markersize (int): Size of dots. """ imagery = OSM() plt.figure(figsize=figsize, dpi=dpi) ax = plt.axes(projection=imagery.crs) ax.set_extent( (min(self.lng) - 0.02, max(self.lng) + 0.02, min(self.lat) - 0.01, max(self.lat) + 0.01)) ax.add_image(imagery, zoom) plt.plot(self.lng, self.lat, 'bo', transform=ccrs.Geodetic(), markersize=markersize) plt.title(self.title)
def __init__(self, file: str): self.is_heading_plotted = False self.is_course_plotted = False self.file = file self.transformation = ccrs.PlateCarree() self.color_mapping = 'viridis' self.fMin_latitude = -1 self.fMax_latitude = -1 self.fMin_longitude = -1 self.fMax_longitude = -1 self.fMap_margins = 0.05 # Initializes lists used to store all data for a particular data attribute self.iDate_list = [] self.fTime_list = [] self.fLong_list = [] # x coord self.fLat_list = [] # y coord self.fShip_heading_list = [] self.fShip_course_list = [] self.fShip_speed_list = [] self.fTemperature_list = [] self.fSalinity_list = [] self.fConductivity_list = [] self.fFluorescence_list = [] # *********************************************************************** # * MAP SETUP # *********************************************************************** # Get map tiles from the OpenStreetMap Server, map tiles allow for maps to be loaded from servers self.osm_tiles = OSM() # Changes the display size - specifically figsize=(x,y) self.fig = plt.figure(figsize=(12, 6)) # Create a GeoAxes in the tile's projection self.ax = plt.axes(projection=self.osm_tiles.crs)
def create_plot(polygons_df, shape_func, datas, data_plot_funcs, color_pallete, title, extend, sav_figname, **kargs): ''' Creates plot Parameters ---------- shapes_in : list of shapely.geometry.multipolygon.MultiPolygon Shapes that represent region of interest. shapes_out : list of shapely.geometry.multipolygon.MultiPolygon All shaps or not in region of interest shapes. shape_func : function Function that determines how to represent shapes in regions of interest. Parameters: ax, shapes_in, color_pallette, **kargs datas : list of obj pd.DataFrame Data that needs to be plotted on the plot data_plot_funcs : function Function that determines how to represent data at plot. Parameters: ax, data, color_palleter, **kargs color_pallette : dict Dictionary of colors title : str Title of plot extend : list of float Extend of map sav_figname : str Name of file in which the plot will be saved Keyword argumnets ----------------- A bunch of ^^ graph_info : str Infromation about data sources Returns ------- ''' zorders = { 'legends':100, 'ground_in':5, 'ground_out':20, 'water_out':15, 'water_in':1, 'roads_major_out':22, 'roads_minor_out':21, 'roads_major_in':7, 'roads_minor_in':6, 'first_layer_top':25, 'first_layer':20, 'first_layer_bottom':15, 'second_layer_top':10, 'second_layer':5, 'second_layer_bottom':1, } tiler = OSM() plt.figure(figsize=(10,10)) fig = plt.gcf() ax = plt.axes(projection=tiler.crs) ax.coastlines('10m') ax.set_extent(extend) shape_func(ax, polygons_df, color_pallete, zorder = zorders, tiler=tiler, **kargs) legend_lines_list = [] legend_text_list = [] for indx, record in enumerate(list(zip(datas, data_plot_funcs))): legend_line, legend_text = record[1](ax, record[0], color_pallete, fig = fig, ind = indx, **kargs ) legend_lines_list.append(legend_line) legend_text_list.append(legend_text) ax.legend(legend_lines_list,legend_text_list,loc=4).set_zorder( zorders['legends']) if 'graph_info' in kargs and len(kargs['graph_info'])>0: props = dict(boxstyle='round', facecolor='gray', alpha=0.5) ax.text(0.02,0.98,kargs['graph_info'],fontsize=7, horizontalalignment = 'left', verticalalignment = 'top', bbox=props, transform = ax.transAxes, color='White', zorder=1000) ax.text(0.001, 0.001, '© OpenStreetMap contributors', fontsize=8, horizontalalignment = 'left', verticalalignment = 'bottom', transform = ax.transAxes, color='White', zorder=1000) plt.savefig(sav_figname) return ax plt.close() # This code can be used to test create_cmap function #hex_s='dbd1c9' #mcm = create_cmap(['#dbd1c9','#db9a8e','#db6767','#db4848','#ff3a3a']) # #plt.figure() # #plt.scatter(np.random.random(100),np.random.random(100),c=np.random.random(100),cmap=mcm) #plt.colorbar() #plt.show() # #rgba = mcm(np.linspace(0, 1, 256)) #fig, ax = plt.subplots(figsize=(4, 3), constrained_layout=True) #col = ['r', 'g', 'b'] #for xx in [0.25, 0.5, 0.75]: # ax.axvline(xx, color='0.7', linestyle='--') #for i in range(3): # ax.plot(np.arange(256)/256, rgba[:, i], color=col[i]) #ax.set_xlabel('index') #ax.set_ylabel('RGB')
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Tue Jul 28 20:34:00 2020 @author: admin """ import matplotlib.pyplot as plt import cartopy.crs as ccrs from cartopy.io.img_tiles import OSM imagery = OSM() fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection=imagery.crs) ax.set_extent([13.0, 13.8, 52.0, 52.4], ccrs.PlateCarree()) ax.add_image(imagery, 14) plt.show()
from cartopy.io.img_tiles import OSM import cartopy.feature as cfeature from cartopy.io import shapereader from cartopy.io.img_tiles import StamenTerrain from cartopy.io.img_tiles import GoogleTiles #from owslib.wmts import WebMapTileService from matplotlib.path import Path import matplotlib.patheffects as PathEffects import matplotlib.patches as mpatches import numpy as np # %% plt.figure(figsize=(13,6.2)) tiler = OSM() #GoogleTiles() mercator = tiler.crs ax = plt.axes(projection=mercator) #ax.set_extent(( 153, 153.2, -26.6, -26.4)) zoom = 3 ax.add_image(tiler, zoom ) # even 1:10m are too coarse for .2 degree square #ax.coastlines('10m') home_lat, home_lon = 0,0 # Add a marker for home #plt.plot(home_lon, home_lat, marker='o', color='red', markersize=5, alpha=0.7, transform=ccrs.Geodetic())
def get_gif(access_token: str, min_lon: float, max_lat: float, max_lon: float, min_lat: float, ratio: float, colour: str, backgroundColour: str, alpha: float, activity_type: str, bg_img: str, duration: int): activities = requests.get('https://www.strava.com/api/v3/activities' + '?access_token=' + access_token + '&per_page=200' + '&page=' + str(1)) activities = activities.json() # convert activities to pandas dataframe df = json_normalize(activities) # filter df by type of activity if activity_type == 'Run': df = df[df['type'] == 'Run'] elif activity_type == 'Ride': df = df[df['type'] == 'Ride'] else: df = df[(df['type'] == 'Run') | (df['type'] == 'Ride')] # filter df by start coordinates using the bounding box df_bbox = df[(df['start_latitude'] < max_lat) & (df['start_latitude'] > min_lat) & (df["start_longitude"] < max_lon) & (df["start_longitude"] > min_lon)] df_bbox = df_bbox.sort_values(by=['start_date']) # create imagery based on bg_img if bg_img == 'sat': imagery = GoogleTiles(style='satellite') elif bg_img == 'osm': imagery = OSM() else: imagery = OSM() # create figure to plot routes on fig = plt.figure(figsize=(8, ratio * 8), frameon=False) ax = fig.add_subplot(1, 1, 1, projection=imagery.crs) fig.patch.set_visible(False) ax.set_extent([min_lon, max_lon, min_lat, max_lat]) ax.set_axis_off() # filepaths fp_out = 'image.gif' imgs = [] for i in range(len(df_bbox)): try: lat, lng = zip( *polyline.decode(df_bbox.iloc[i]['map.summary_polyline'])) except: print(i) plt.plot(lng, lat, transform=ccrs.Geodetic(), color=colour, alpha=alpha) imgs.append(fig2img(fig)) # create background image if bg_img == 'none': bg = Image.new(mode='RGBA', size=imgs[0].size, color=ImageColor.getrgb(backgroundColour)) else: fig = plt.figure(figsize=(8, ratio * 8), frameon=False) ax = fig.add_subplot(1, 1, 1, projection=imagery.crs) ax.set_extent([min_lon, max_lon, min_lat, max_lat]) fig.patch.set_visible(False) ax.set_axis_off() # set background imagery if one was sent ax.add_image(imagery, 15) # converting background to image bg = fig2img(fig) imgs = map(lambda img: Image.alpha_composite(bg, img), imgs) bg.save(fp=fp_out, format='GIF', append_images=imgs, save_all=True, duration=duration, loop=0) file = open('image.gif', 'rb') return {'gif': base64.b64encode(file.read())}
def getAx(self): def newGetImage(self, tile): if six.PY3: from urllib.request import urlopen, Request else: from urllib2 import urlopen url = self._image_url(tile) # added by H.C. Winsemius req = Request(url) # added by H.C. Winsemius req.add_header('User-agent', 'your bot 0.1') # fh = urlopen(url) # removed by H.C. Winsemius fh = urlopen(req) im_data = six.BytesIO(fh.read()) fh.close() img = Image.open(im_data) img = img.convert(self.desired_tile_form) return img, self.tileextent(tile), 'lower' def getBoundsZoomLevel(bound, mapDim): WORLD_DIM = {"height": 256, "width": 256} ZOOM_MAX = 20 def latRad(lat): sin = math.sin(lat * math.pi / 180) radX2 = math.log((1 + sin) / (1 - sin)) / 2 return max(min(radX2, math.pi), -math.pi) / 2 def zoom(mapPx, worldPx, fraction): return math.floor( math.log(mapPx / worldPx / fraction) / math.log(2)) # 計算採用googlemap格式 (緯度,經度) # 右上 ne = {"lat": bound[3], "lng": bound[1]} # 左下 sw = {"lat": bound[2], "lng": bound[0]} latFraction = (latRad(ne["lat"]) - latRad(sw["lat"])) / math.pi lngDiff = ne["lng"] - sw["lng"] lngFraction = ((lngDiff + 360) / 360) if lngDiff < 0 else (lngDiff / 360) latZoom = zoom(mapDim["height"], WORLD_DIM["height"], latFraction) lngZoom = zoom(mapDim["width"], WORLD_DIM["width"], lngFraction) return min(latZoom, lngZoom, ZOOM_MAX) def getLngLatBounds(): # ! # 迭代各dataframe 回傳經緯度min/max (minx/maxx/miny/maxy) bound = [9999, 0, 9999, 0] # 預設值 for df in self.df_set: if len(df) != 0: bounds = df.geometry.bounds # 檢查各值 有更大的框則更新 bound = [ min(bounds.minx) if min(bounds.minx) < bound[0] else bound[0], # 最小經度 max(bounds.maxx) if max(bounds.maxx) > bound[1] else bound[1], # 最大經度 min(bounds.miny) if min(bounds.miny) < bound[2] else bound[2], # 最小緯度 max(bounds.maxy) if max(bounds.maxy) > bound[3] else bound[3] ] # 最大緯度 # margin_lng = (bound[0] - bound[1]) * 0.03 # margin_lat = (bound[2] - bound[3]) * 0.03 # bound = list(map(lambda x, y: x + y, bound, [-margin_lng, +margin_lng, -margin_lat, +margin_lat])) return bound # 沒有自訂bound 使用資料之最大邊界為bound if len(self.bound) == 0: self.bound = getLngLatBounds() # fig與legend固定4比1 # 字體大小12pt # legend超出則裁切 # 解決matplotlib本身不支援中文字體 會顯示成方塊的問題 # see: https://stackoverflow.com/questions/10960463/non-ascii-characters-in-matplotlib plt.rcParams['axes.unicode_minus'] = False # 解決負號 '-' 顯示為方塊的問題 plt.rc( 'font', **{ 'sans-serif': 'Microsoft JhengHei', # 指定中文字體 (微軟正黑體) 'family': 'sans-serif', 'size': 12 }) # 指定默認字型 self.dpi = 300 fig = plt.figure(dpi=self.dpi) ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) ax.set_title(self.title) ax.set_extent(self.bound, ccrs.PlateCarree()) # 獲取fig框像素大小 fig_size = fig.get_size_inches() * fig.dpi mapDim = {"height": int(fig_size[0]), "width": int(fig_size[1])} # 計算zoom_level zoom_lv = getBoundsZoomLevel(self.bound, mapDim) print("計算最佳zoom_level: ", zoom_lv) OSM.get_image = newGetImage imagery = OSM() # interpolation: matplotlib抗鋸齒 # 0.18版更新 可修正下述bug # cartopy0.18版interpolation的bug # see: https://github.com/SciTools/cartopy/issues/1563 # cartopy0.17版add_image的bug # see: https://github.com/SciTools/cartopy/issues/1341 inter = 'spline36' # regrid_shape: basemap長寬之短邊尺寸 regrid = max(mapDim.values()) # ax.add_image(imagery, zoom, interpolation=inter, regrid_shape=regrid) #ax.add_image(imagery, zoom_lv, regrid_shape=regrid) ax.add_image(imagery, zoom_lv) # 色碼表: https://www.ebaomonthly.com/window/photo/lesson/colorList.htm return ax
# import urllib.request # import json # import matplotlib.pyplot as plt # from matplotlib import animation # import cartopy.crs as ccrs # from cartopy.io.img_tiles import OSM import cartopy.crs as ccrs from cartopy.io.img_tiles import OSM import matplotlib.pyplot as plt osm_tiles = OSM() plt.figure(figsize=(16, 16)) # Use the tile's projection for the underlying map. ax = plt.axes(projection=osm_tiles.crs) # Specify a region of interest, in this case, Cardiff. ax.set_extent([-122.335058, -122.222542, 47.426043, 47.472231], ccrs.PlateCarree()) # Add the tiles at zoom level 12. ax.add_image(osm_tiles, 12, interpolation='spline36') ax.coastlines('10m') plt.show() # #DEFINE FIGURE
def main(): """ lon0 = 35.25; lat0 = -23.60 lon1 = 35.25; lat1 = -24.0 lon2 = 35.65; lat2 = -24.0 lon3 = 35.65; lat3 = -23.60 https://stackoverflow.com/questions/52356926/how-to-set-offset-for-python-cartopy-geometry """ # Create the figure fig = plt.figure(figsize=(5,6)) from cartopy.io.img_tiles import OSM # Add OSM image as background for the selected extension tiler = OSM() ax = plt.axes(projection=tiler.crs) # Openstreet layer zoom detail zoom=12 ax.add_image(tiler, zoom, alpha=0.75) extent = [35.25, 35.65, -24.05, -23.60] ax.set_extent(extent) # Set figure title ax.set_title('Study area',fontsize=10) # Plot coastline from GSHHSF coast_line=cfeature.GSHHSFeature(scale='full') ax.add_feature(coast_line, alpha=1.0, linewidths=0.5, edgecolor='black' ) plotWetlands(ax) plotRivers(ax) plotInterestPoint(ax) # plot difters track plotTrack(ax) # Plot escale bar scaleBar(ax, length=10, location=(0.8, 0.80), linewidth=1.5) plotGridLines(ax) # Criate a subfigure #subfigure = [.58, .09, .30, .20] ax2 = fig.add_axes([.58, .09, .30, .20]) plotMozambique(ax2) # Adjust plot the the figure plt.tight_layout() plt.show() saveFig(fig, file='estudyarea')
def plot_static(self, format="png", dpi=150): '''Creates a map of the contact list using a cartopy map.''' # Plotting work horse, basically we want a big plot and then a zoom in # on contact area def do_plot(ax, t1s, t2s, contact_ts, extend, imagery, level=14): '''Add to axis''' ax.set_extent(extend) ax.add_image(imagery, level) lons = t1s["longitude"].to_numpy() lats = t1s["latitude"].to_numpy() size = t1s["accuracy"].to_numpy() # do coordinate conversion of (lat,y) xynps = ax.projection.transform_points(ccrs.Geodetic(), lons, lats) ax.scatter(xynps[:, 0], xynps[:, 1], size, marker='o', alpha=.5, color="blue", label="Patient") lons = t2s["longitude"].to_numpy() lats = t2s["latitude"].to_numpy() size = t2s["accuracy"].to_numpy() # do coordinate conversion of (x,y) xynps = ax.projection.transform_points(ccrs.Geodetic(), lons, lats) ax.scatter(xynps[:, 0], xynps[:, 1], size, marker='o', alpha=.5, color="green", label="Infected") lons = contact_ts["longitude"].to_numpy() lats = contact_ts["latitude"].to_numpy() size = contact_ts["accuracy"].to_numpy() # do coordinate conversion of (x,y) xynps = ax.projection.transform_points(ccrs.Geodetic(), lons, lats) ax.scatter(xynps[:, 0], xynps[:, 1], size, marker='o', alpha=.5, color="red", label="Contacts") def safe_concat(l): if len(l) > 0: return pd.concat(l) else: return pd.DataFrame( columns=['time', 'longitude', 'latitude', 'accuracy']) contact_ts = safe_concat([ pd.DataFrame(c.trajectory(), columns=['time', 'longitude', 'latitude', 'accuracy'], dtype=np.float64) for c in self ]) # It should not be needed to plot if there are no contacts if not len(contact_ts): return None t1s = safe_concat([c.t1.pd_frame for c in self if len(c.t1.data) > 0]) t2s = safe_concat([c.t2.pd_frame for c in self if len(c.t2.data) > 0]) # We are going to plot addition zoom in plots on clusters of # contacts. A cluster of contacts are events seperated by edge # of large distance. We say distance is large relative to max blue # and green distances contact_ts = contact_ts.sort_values(by='time').reset_index(drop=True) all = pd.concat([t1s, t2s, contact_ts]) if len(all) == 0: return None # This would be related to window size max_separation = max(get_max_dist_meters(t1s), get_max_dist_meters(t2s)) # We can form a possible cluster iff we make a large step edge_lengths = conseq_distance_meters(contact_ts) # There will always be at least to global plot slack_lon = 0.015 slack_lat = 0.005 extends = [(min(all["longitude"]) - slack_lon, max(all["longitude"]) + slack_lon, min(all["latitude"]) - slack_lat, max(all["latitude"]) + slack_lat)] # For just one contact we zoom in if not len(edge_lengths): extends.append((min(all["longitude"]) - slack_lon / 10., max(all["longitude"]) + slack_lon / 10., min(all["latitude"]) - slack_lat / 10., max(all["latitude"]) + slack_lat / 10.)) # So will we zoom in? elif any(e > 0.5 * max_separation for e in edge_lengths): # A subplot has different extends slack_lon /= 10. slack_lat /= 10. # Subplots will be for slices based on break points bpts = [0] + [ p[0] for p in enumerate(edge_lengths, 1) if p[1] > 0.5 * max_separation ] + [len(contact_ts)] for start, end in zip(bpts[:-1], bpts[1:]): cluster = contact_ts.iloc[start:end] extends.append((min(cluster["longitude"]) - slack_lon, max(cluster["longitude"]) + slack_lon, min(cluster["latitude"]) - slack_lat, max(cluster["latitude"]) + slack_lat)) imagery = OSM() fig = plt.figure(figsize=(8, 8)) # Fill nplots = len(extends) for idx, extend in enumerate(extends, 1): ax = fig.add_subplot(nplots, 1, idx, projection=imagery.crs) do_plot(ax, t1s, t2s, contact_ts, extend, imagery=imagery, level=14 if idx == 1 else 16) # Fine for zoomd f = io.BytesIO() fig.savefig(f, format=format, quality=95, dpi=dpi) f.seek(0) plt.close() return f.getvalue()