【问题标题】:Plot data on satellite maps在卫星地图上绘制数据
【发布时间】:2018-12-20 17:43:41
【问题描述】:

如何在 python(笔记本)中使用高分辨率的卫星背景图像在地图上绘制 (lat, lon, value) 数据?

我在整个互联网上爬行,但找不到任何有用的东西。 Folium 不提供卫星图块。 SimpleKMLgoogleearthplot 似乎只对巨大的低分辨率地球数据有用。 EarthPy 可以接受图像图块,但它们与 NASA 网站的链接仅提供 >0.1 度的低分辨率图像。 Cartopy 是 matplotlib 用户的新希望,但我找不到任何卫星图像切片的示例。

挫败感特别大,因为使用R,使用RGoogleMaps 包,这项工作非常简单,例如:

plotmap(lat, lon, col=palette(value), data=mydataframe, zoom = 17, maptype="satellite")

我们如何在 Python 中做到这一点?

【问题讨论】:

  • 您可以将 plotly 与 mapbox 一起使用。您将需要一个 mapbox 访问令牌。在链接中的第三个示例中,只需将 style='light' 更改为 style='satellite'
  • 您可以借助rpy2 包和%%R 单元魔法在Python Jupyter notebook 中访问R 代码。在此处查看详细信息stackoverflow.com/questions/39008069/…
  • 下面我将赏金和正确答案授予帕萨。但是,这两个答案都以某种方式解决了问题,而且两者都应得的。同时,还有改进的余地。我愿意接受更新的答案,并为更好的解决方案奖励另一个赏金。

标签: python pandas google-maps plot maps


【解决方案1】:

另一种选择是使用gmplot。它基本上是一个围绕 Google Maps javascript API 的 python 包装器,允许您生成 .html 文件,这些文件在后台使用地图呈现您的地块。

在这里,我用它来绘制卫星图像背景下的随机游走(默认情况下不支持这种地图类型,但让它工作起来非常简单):

from gmplot import GoogleMapPlotter
from random import random

# We subclass this just to change the map type
class CustomGoogleMapPlotter(GoogleMapPlotter):
    def __init__(self, center_lat, center_lng, zoom, apikey='',
                 map_type='satellite'):
        super().__init__(center_lat, center_lng, zoom, apikey)

        self.map_type = map_type
        assert(self.map_type in ['roadmap', 'satellite', 'hybrid', 'terrain'])

    def write_map(self,  f):
        f.write('\t\tvar centerlatlng = new google.maps.LatLng(%f, %f);\n' %
                (self.center[0], self.center[1]))
        f.write('\t\tvar myOptions = {\n')
        f.write('\t\t\tzoom: %d,\n' % (self.zoom))
        f.write('\t\t\tcenter: centerlatlng,\n')

        # This is the only line we change
        f.write('\t\t\tmapTypeId: \'{}\'\n'.format(self.map_type))


        f.write('\t\t};\n')
        f.write(
            '\t\tvar map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);\n')
        f.write('\n')

initial_zoom = 16
num_pts = 40

lats = [37.428]
lons = [-122.145]
for pt in range(num_pts):
    lats.append(lats[-1] + (random() - 0.5)/100)
    lons.append(lons[-1] + random()/100)
gmap = CustomGoogleMapPlotter(lats[0], lons[0], initial_zoom,
                              map_type='satellite')
gmap.plot(lats, lons, 'cornflowerblue', edge_width=10)

gmap.draw("mymap.html")

您可以在浏览器中打开生成的 .html 文件,并像使用 Google 地图一样进行交互。 不幸的是,这意味着你不会得到一个漂亮的matplotlib 图形窗口或任何东西,所以为了生成一个图像文件,你需要自己截取屏幕截图或破解一些东西来为你呈现 HTML。

要记住的另一件事是,您可能需要Google Maps API key,否则您最终会像我一样得到一张丑陋的深色水印地图:

另外,由于您想将值描述为颜色,您需要手动将它们转换为颜色字符串并使用gmap.scatter() 方法。如果您对这种方法感兴趣,请告诉我,以便我尝试编写一些代码来做到这一点。

更新

这是一个支持将值编码为卫星图像散点图中颜色的版本。为了达到这个效果,我使用了matplotlib 的颜色图。您可以根据需要更改颜色图,请参阅选项列表 here。我还包含了一些代码来从文件apikey.txt 中读取 API 密钥,这允许每个研究人员在不更改代码的情况下使用自己的个人密钥(如果没有找到这样的文件,则像往常一样默认为没有 API 密钥)。

import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
from matplotlib.cm import ScalarMappable
from gmplot import GoogleMapPlotter
from random import random


class CustomGoogleMapPlotter(GoogleMapPlotter):
    def __init__(self, center_lat, center_lng, zoom, apikey='',
                 map_type='satellite'):
        if apikey == '':
            try:
                with open('apikey.txt', 'r') as apifile:
                    apikey = apifile.readline()
            except FileNotFoundError:
                pass
        super().__init__(center_lat, center_lng, zoom, apikey)

        self.map_type = map_type
        assert(self.map_type in ['roadmap', 'satellite', 'hybrid', 'terrain'])

    def write_map(self,  f):
        f.write('\t\tvar centerlatlng = new google.maps.LatLng(%f, %f);\n' %
                (self.center[0], self.center[1]))
        f.write('\t\tvar myOptions = {\n')
        f.write('\t\t\tzoom: %d,\n' % (self.zoom))
        f.write('\t\t\tcenter: centerlatlng,\n')

        # Change this line to allow different map types
        f.write('\t\t\tmapTypeId: \'{}\'\n'.format(self.map_type))

        f.write('\t\t};\n')
        f.write(
            '\t\tvar map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);\n')
        f.write('\n')

    def color_scatter(self, lats, lngs, values=None, colormap='coolwarm',
                      size=None, marker=False, s=None, **kwargs):
        def rgb2hex(rgb):
            """ Convert RGBA or RGB to #RRGGBB """
            rgb = list(rgb[0:3]) # remove alpha if present
            rgb = [int(c * 255) for c in rgb]
            hexcolor = '#%02x%02x%02x' % tuple(rgb)
            return hexcolor

        if values is None:
            colors = [None for _ in lats]
        else:
            cmap = plt.get_cmap(colormap)
            norm = Normalize(vmin=min(values), vmax=max(values))
            scalar_map = ScalarMappable(norm=norm, cmap=cmap)
            colors = [rgb2hex(scalar_map.to_rgba(value)) for value in values]
        for lat, lon, c in zip(lats, lngs, colors):
            self.scatter(lats=[lat], lngs=[lon], c=c, size=size, marker=marker,
                         s=s, **kwargs)


initial_zoom = 12
num_pts = 40

lats = [37.428]
lons = [-122.145]
values = [random() * 20]
for pt in range(num_pts):
    lats.append(lats[-1] + (random() - 0.5)/100)
    lons.append(lons[-1] + random()/100)
    values.append(values[-1] + random())
gmap = CustomGoogleMapPlotter(lats[0], lons[0], initial_zoom,
                              map_type='satellite')
gmap.color_scatter(lats, lons, values, colormap='coolwarm')

gmap.draw("mymap.html")

作为示例,我使用了一系列单调递增的值,这些值在 coolwarm 颜色图中很好地从蓝色阴影映射到红色:

【讨论】:

  • 谢谢,这个例子效果很好,如果有像上面这样的例子(点和颜色),那就太好了!我仍然对 API Key 概念感到失望。我们正在与数百名同事一起在科学研究中使用这些地图来绘制和分析数据。当我们与世界各地的同事公开分享脚本时,将密钥绑定到一个人是没有意义的。按负载付费也没有意义,因为我们在分析过程中绘制了数百次数据只是为了快速查看......总之我想知道为什么RGoogleMaps 在这些条件下是免费使用的。 .
  • 我了解收取服务费用的公司,因为获取和维护所有这些地图可能并不便宜。但是我同意一些用例应该免费(例如使用率低)。事实上,按负载付费的方案并不是最优的。我看了一下R包,它似乎通过重用地图瓦片来支持离线绘图,这很聪明。我已经编辑了我的答案,包括将值编码为颜色的选项和 API 密钥文件功能,以允许不同的用户使用他们自己的私钥。如果任何答案符合您的需求,请考虑接受一个。
  • @pasa 我想实现一些东西,我点击地图上的某处并绘制一个点(并返回该点的纬度)而不是给它经度坐标。你知道如何实现吗?
  • @bakalolo 我以前从未这样做过,但我怀疑将 lat long 返回 python 并不是一件容易的事(如果这就是你要问的),因为用户与在浏览器中映射。我建议您发布您自己的问题,也许其他人可以提供帮助。
【解决方案2】:

使用散景,据我所知,使用 GMAP 卫星图块可能是最简单的方法。

from bokeh.io import output_notebook, show
from bokeh.models import ColumnDataSource, GMapOptions, HoverTool
from bokeh.plotting import gmap, figure

output_notebook()

api_key = your_gmap_api_key

您的地图选项

map_options = GMapOptions(lat=47.1839600, lng= 6.0014100, map_type="satellite", zoom=8, scale_control=True)

添加一些工具以获得交互式地图

hover=HoverTool(tooltips=[("(x,y)","($x,$y)")])

tools=[hover, 'lasso_select','tap']

创建地图并对其进行自定义

p = gmap(api_key, map_options, title="your_title", plot_height=600, plot_width=1000, tools=tools)
p.axis.visible = False
p.legend.click_policy='hide'

添加您的数据

your_source = ColumnDataSource(data=dict(lat=your_df.lat, lon=your_df.lon, size = your_df.value))

p.circle(x="lon",y="lat",size=size, fill_color="purple",legend = "your_legend", fill_alpha=0.2, line_alpha=0, source=your_source)
show(p)

【讨论】:

    【解决方案3】:

    通过注册 Mapbox (mapbox.com) 并使用他们提供的 API 密钥,您可以获得 folium 以使用自定义图块集(他们的 API_key=tile='Mapbox' 参数似乎对我不起作用)。

    例如这对我有用(但是公开地图的分辨率会因位置而异):

    import folium
    
    mapboxAccessToken = 'your api key from mapbox'
    
    mapboxTilesetId = 'mapbox.satellite'
    
    
    m = folium.Map(
        location=[51.4826486,12.7034238],
        zoom_start=16,
        tiles='https://api.tiles.mapbox.com/v4/' + mapboxTilesetId + '/{z}/{x}/{y}.png?access_token=' + mapboxAccessToken,
        attr='mapbox.com'
    )
    
    tooltip = 'Click me!'
    
    folium.Marker([51.482696, 12.703918], popup='<i>Marker 1</i>', tooltip=tooltip).add_to(m)
    folium.Marker([51.481696, 12.703818], popup='<b>Marker 2</b>', tooltip=tooltip).add_to(m)
    
    m
    

    我从未真正使用过 Mapbox,但如果你碰巧有想要使用的图像,你甚至可以创建自己的图块集。

    注意:我首先在笔记本安装 folium 中运行了这个:

    import sys
    !{sys.executable} -m pip install folium
    

    回应cmets:

    • Mapbox 是一家提供定位和地图服务的公司(正如我所提到的,我从未使用过它们,我想您可以在https://www.mapbox.com 找到更多信息)
    • Mapbox 需要令牌,因为它不是无限制的免费服务...即他们给你一个令牌来跟踪请求......如果你使用的超过免费分配中包含的内容,我猜他们会限制你的帐户
    • “v4”只是 Mapbox API 路由的一部分。我猜他们也有 v1、v2 等。
    • 是否有更新版本的图块?我不确定,我想你可以看看 Mapbox 的文档。看起来您也可以将自己的地图上传到 Mapbox,他们会存储它们并将其返回给您。
    • 如何在输出中添加 x-/y- 轴?我不太确定。但是 folium 是 LeafletJS 的包装,这是一个流行的库,有很多 plugins。编写一个类来包装任何 LeafetJS 插件看起来并不难(参见开箱即用的示例here),所以也许您可以找到一个适合您的问题并自己包装它?

    【讨论】:

    • 谢谢,这个例子有效。要获得赏金,请详细说明原因和方法:什么是 Mapbox?为什么它需要令牌?链接到潜在的瓷砖。您的代码中的“v4”是什么意思,是否有更新版本的图像(例如 Landsat)?如何将 x 轴和 y 轴添加到地图(如问题中)?
    • 感谢您的回答。我很不高兴,因为它看起来像是一团糟的额外工作和摆弄,只是为了有一张卫星地图,你可以在一行代码中在 R 中拥有它。我仍然希望python中还有另一种解决方案。如果没有,您将获得赏金。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-06
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多