从R中生成的Leaflet地图的json加载Quarto html地图数据

Sam*_*amR 7 javascript r leaflet r-leaflet quarto

我创建了一篇四开版博客文章,其中包含许多leaflet在 R 中生成的地图。由于每个地图的数据都嵌入在 html 文件中,因此文件本身非常大。这会导致托管该文件的服务器出现问题。

我想让html文件变小。embed-resources: false Quarto 中的YAML 选项意味着库(例如leaflet.js)存储在单独的文件中。有帮助,但数据仍然存储在 html 中(每个地图一次)。我正在尝试从单独的文件加载数据本身。这是一个最小的文件示例qmd

---
format:
  html:
    embed-resources: false  
---

```{r}
leaflet::leaflet(elementId = "map1") |>
    leaflet::addTiles() |>
    leaflet::addMarkers(lng = 174.768, lat = -36.852, popup = "The birthplace of R")
```
Run Code Online (Sandbox Code Playgroud)

当我quarto render这样做时,它会创建一个 html 文件,该文件在浏览器中打开时显示地图。该文件包含以下地图数据<div>

<div class="leaflet html-widget html-fill-item-overflow-hidden html-fill-item" id="map1" style="width:100%;height:464px;"></div>
<script type="application/json" data-for="map1">{**json**}</script>
</div>
Run Code Online (Sandbox Code Playgroud)

我写的地方{**json**}有一长串 json,其中包含地图坐标、CRS 和各种选项。

在我看来,我也许可以将 json 内容复制到文件中,然后更改标签<script>以从该文件加载数据:

---
format:
  html:
    embed-resources: false  
---

```{r}
leaflet::leaflet(elementId = "map1") |>
    leaflet::addTiles() |>
    leaflet::addMarkers(lng = 174.768, lat = -36.852, popup = "The birthplace of R")
```
Run Code Online (Sandbox Code Playgroud)

然而,我现在知道这是不可能的。相反,我尝试添加一个脚本将 json 注入到innerHTML所需元素中(使用Live Server进行测试):

<div class="leaflet html-widget html-fill-item-overflow-hidden html-fill-item" id="map1" style="width:100%;height:464px;"></div>
<script type="application/json" data-for="map1">{**json**}</script>
</div>
Run Code Online (Sandbox Code Playgroud)

其工作原理是,它将准确的 json 内容加载到标签中,就像硬编码到 html 文件中一样(需要replaceAll()使其相同,因为在反斜杠之前添加了几个转义字符)。

然而,仅此并不能显示地图,并且控制台会抛出此错误:

Uncaught SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at htmlwidgets.js:646:27
Run Code Online (Sandbox Code Playgroud)

相关行htmlwidgets.js

var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
var data = JSON.parse(scriptData.textContent || scriptData.text);
Run Code Online (Sandbox Code Playgroud)

即,在加载的脚本正在查找数据时,请求fetch()尚未更新innerHTML标签<script data-for="map1"></script>,因此没有任何内容可解析。

考虑到这一点,以及fetch()请求,我移动了htmlwidgets.js和其他<script>标签以尝试延迟它们的加载。目前大概有10行这样的标签<head>

<script src="page_files/libs/htmlwidgets-1.6.2/htmlwidgets.js"></script>
<script src="page_files/libs/jquery-1.12.4/jquery.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

如果我将它们从 移到和标签<head>之间,地图的渲染时间大约会减少一半。所以看起来它们的加载和将 json 注入到标签中的脚本之间存在某种竞争。</body></html><script data-for="map1"></script>

为了确保加载按正确的顺序发生,我从 html 中删除了脚本<head>,并使用异步loadScript()函数动态加载脚本,以确保它们仅在数据加载后加载:

fetch('./map1.json')
   .then((response) => response.json())
   .then((json) => (
document.querySelectorAll('[data-for="map1"]')[0].innerHTML = JSON.stringify(json).replaceAll("\/", "/"))) 
  .then(() =>    
loadScript("page_files/libs/htmlwidgets-1.6.2/htmlwidgets.js")
      ).then(() =>        
loadScript("page_files/libs/jquery-1.12.4/jquery.min.js"));
/*etc - for all scripts on the page in the order they appear in the html*/
Run Code Online (Sandbox Code Playgroud)

现在,脚本仅在将 json 注入<script data-for="map1"></script>标签后才加载。但是,它根本不渲染地图,并且 html 小部件未注册(即document.getElementById("map1").htmlwidget_data_init_result在控制台中返回undefined)。

我是否遗漏了有关事件在静态四开本生成的网页上发生的顺序的信息htmlwidgets

有没有办法让 Quarto html 文件leaflet从 json 文件加载 R 中生成的地图的数据并渲染地图?

Sam*_*amR 7

不可避免地,在悬赏两天后我找到了解决方案。这种方法将我的实际 html 文件从 22.5mb 减少到 165kb。步骤是:

  1. 删除html<script src = "*.js">中的所有标签<head>,将要加载的 URL 存储在步骤 4 中的数据后面(以避免加载时出现没有数据可解析的问题)。
  2. 找到硬编码到 html 正文中的标签中的 JS <script>,并将代码移动到要在步骤 4 中加载的单独 *.js 文件中(以防止在已加载的脚本之前加载它们而导致错误<head>)。
  3. htmlwidgets从标签中删除硬编码的地图(以及任何其他)json 数据<script type="application/json">,并将其保存到文件夹中的单独 json 文件中./page_files/data/
  4. 将 JS 脚本插入到<head>使用Promises和链式.then()语句来执行以下操作(按此顺序):
    • 将每个 json 文件中的相关数据注入回 html 中。
    • 动态加载<head>.
    • 动态加载<body>.
    • 使用 渲染所有元素HTMLWidgets.staticRender()

我编写了一个 Python 脚本来自动处理任何 html 文件。这可以作为post-render选项添加到 Quarto 项目 YAML,例如:

project:
  type: website
  post-render: remove_hardcoded_data.py
Run Code Online (Sandbox Code Playgroud)

这将转换文件夹中的所有 html 文件。或者,如果在项目外部使用 Quarto,则可以将其放置在包含一个或多个 html 文件的文件夹中,并使用./remove_hardcoded_data.py.

Python脚本

这需要美丽汤4。它将为文件夹中的所有 html 文件创建一个最小的 html 文件并附加到"_min"输出(例如,如果输入是"./page.html",则输出将为"./page_min.html")。Quarto 已经创建了一个"./page_files/"需要上传到服务器的文件夹,这是脚本复制 json 数据的地方。

files_to_exclude通过将文件添加到函数的列表中,可以将文件从该脚本中排除make_all_html_min()

project:
  type: website
  post-render: remove_hardcoded_data.py
Run Code Online (Sandbox Code Playgroud)

在没有任何答案的情况下继续尝试后我得出了这个结论。一旦我解决了这个问题,我决定自己回答这个问题,以防其他人遇到这个问题。但是,我无法将赏金授予自己,因此我仍然对其他解决方案持开放态度。