<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title> - shiny</title>
    <link rel="self" type="application/atom+xml" href="https://pr-30.preview.ricochet.rs/blog/tags/shiny/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://pr-30.preview.ricochet.rs/blog"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-08T00:00:00+00:00</updated>
    <id>https://pr-30.preview.ricochet.rs/blog/tags/shiny/atom.xml</id>
    <entry xml:lang="en">
        <title>Scalable Spatial Viz: Lonboard and Shiny for Python</title>
        <published>2026-07-08T00:00:00+00:00</published>
        <updated>2026-07-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://pr-30.preview.ricochet.rs/blog/high-performance-spatial/"/>
        <id>https://pr-30.preview.ricochet.rs/blog/high-performance-spatial/</id>
        
        <content type="html" xml:base="https://pr-30.preview.ricochet.rs/blog/high-performance-spatial/">&lt;p&gt;With the release of ricochet v0.6 we&#x27;ve begun stabilizing support for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;shiny.posit.co&#x2F;py&#x2F;&quot;&gt;Shiny for Python&lt;&#x2F;a&gt;, rounding out our existing Shiny support across both R and Python.&lt;&#x2F;p&gt;
&lt;p&gt;To celebrate, we built a demo app (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ricochet-rs&#x2F;test-apps&#x2F;tree&#x2F;main&#x2F;python&#x2F;shiny-py-lonboard&quot;&gt;source&lt;&#x2F;a&gt;) that showcases modern scalable geospatial visualization using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developmentseed.org&#x2F;lonboard&#x2F;&quot;&gt;&lt;code&gt;lonboard&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developmentseed.org&#x2F;&quot;&gt;Development Seed&lt;&#x2F;a&gt;. The app visualizes the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.kaggle.com&#x2F;datasets&#x2F;harlfoxem&#x2F;housesalesprediction&quot;&gt;King County housing sales dataset&lt;&#x2F;a&gt; (~22k home sales) on an interactive map with filtering by price, bedrooms, bathrooms, and square footage.&lt;&#x2F;p&gt;
&lt;p&gt;You can view the app live at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;portal.ricochet.rs&#x2F;app&#x2F;shiny-lonboard&quot;&gt;portal.ricochet.rs&#x2F;app&#x2F;shiny-lonboard&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;!-- TODO screenshot: full app view --&gt;
&lt;p&gt;This blog post will discuss what makes lonboard &lt;em&gt;so fast&lt;&#x2F;em&gt; 🏎️💨.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;GeoJSON adds a &lt;em&gt;ton&lt;&#x2F;em&gt; bloat and is a real bottleneck&lt;&#x2F;li&gt;
&lt;li&gt;Using &quot;end-to-end binary data pipeline&quot; provides meaningful speed ups&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;geoparquet.org&#x2F;&quot;&gt;GeoParquet&lt;&#x2F;a&gt; results in smaller file sizes&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;geoarrow.org&#x2F;&quot;&gt;GeoArrow&lt;&#x2F;a&gt; native represention results in 0 cost data transfer&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;run-the-app-locally&quot;&gt;Run the app locally&lt;&#x2F;h2&gt;
&lt;p&gt;If you want to run the app directly, the source code for the app is in our repo &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ricochet-rs&#x2F;test-apps&#x2F;&quot;&gt;&lt;code&gt;ricochet-rs&#x2F;test-apps&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Clone the example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E0DEF4; background-color: #191724;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; clone&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; --filter=blob:none --sparse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; https:&#x2F;&#x2F;github.com&#x2F;ricochet-rs&#x2F;test-apps.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; test-apps&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; sparse-checkout set python&#x2F;shiny-py-lonboard&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; python&#x2F;shiny-py-lonboard&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Install dependencies and run locally:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E0DEF4; background-color: #191724;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;uv&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; sync&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;uv&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; run shiny run app.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;status-quo&quot;&gt;Status Quo&lt;&#x2F;h2&gt;
&lt;p&gt;Interactive visualization of spatial data has traditionally been done using the library &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;leafletjs.com&#x2F;&quot;&gt;Leaflet&lt;&#x2F;a&gt; (via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;python-visualization.github.io&#x2F;folium&#x2F;latest&#x2F;&quot;&gt;folium&lt;&#x2F;a&gt; in python and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rstudio.github.io&#x2F;leaflet&#x2F;&quot;&gt;&lt;code&gt;{leaflet}&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; in R). There are other alternative such as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;deckgl.readthedocs.io&#x2F;en&#x2F;latest&#x2F;&quot;&gt;&lt;code&gt;pydeck&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;keplergl&#x2F;kepler.gl&quot;&gt;kepler.gl&lt;&#x2F;a&gt;. The R community has also begun transitioning away from &lt;code&gt;{leaflet}&lt;&#x2F;code&gt; towards the newer and modern &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;walker-data.com&#x2F;mapgl&#x2F;&quot;&gt;&lt;code&gt;{mapgl}&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;However, &lt;strong&gt;all of&lt;&#x2F;strong&gt; these libraries still suffer from the same bottleneck: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;geojson.org&#x2F;&quot;&gt;GeoJSON&lt;&#x2F;a&gt;.
GeoJSON is an extension of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.json.org&#x2F;json-en.html&quot;&gt;JSON&lt;&#x2F;a&gt; (JavaScript Object Notation).
It is a &lt;em&gt;pure text&lt;&#x2F;em&gt; representation of spatial data. And frankly, text &lt;strong&gt;is not&lt;&#x2F;strong&gt; an efficient way to store spatial data—a shapefile is likely better.&lt;&#x2F;p&gt;
&lt;p&gt;There are two primary issues with GeoJSON:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;It is text which grows in size very quickly&lt;&#x2F;li&gt;
&lt;li&gt;Data in GeoJSON needs to be &lt;em&gt;parsed&lt;&#x2F;em&gt; into an in-memory representation&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Converting an in memory object such as a data frame (e.g. &lt;code&gt;GeoDataFrame&lt;&#x2F;code&gt; or &lt;code&gt;sf&lt;&#x2F;code&gt;) to another format is called &lt;strong&gt;serialization&lt;&#x2F;strong&gt;.
Serializing to GeoJSON can be &lt;em&gt;very&lt;&#x2F;em&gt; slow. On the flip side, reading from GeoJSON—a.k.a. &lt;strong&gt;deserialization&lt;&#x2F;strong&gt;—itself is even slower.
Other file formats such as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;flatgeobuf.org&#x2F;&quot;&gt;FlatGeobuf&lt;&#x2F;a&gt; enable users to read only relevant portions of the file (random access).&lt;&#x2F;p&gt;
&lt;p&gt;Most browser-based mapping tools (Leaflet, MapLibre, pydeck, etc) require your data to arrive as GeoJSON.&lt;&#x2F;p&gt;
&lt;p&gt;This results in a workflow like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;
flowchart LR
A[GeoDataFrame] --&amp;gt; B[GeoJSON]
B --&amp;gt; C[Send to the browser]
C --&amp;gt; D[Parse the GeoJSON]
D --&amp;gt; E[Render]
&lt;&#x2F;pre&gt;
&lt;p&gt;The (de)serialization steps take up the bulk of the time.&lt;&#x2F;p&gt;
&lt;p&gt;The whole workflow is fraught.
Serializing a GeoDataFrame toe GeoJSON is slow. The string itself is large.
Parsing it in the browser is slow. This is not noticeable for small data. But but the time you have tens of thousands of features, the round-trip becomes a bottleneck.&lt;&#x2F;p&gt;
&lt;p&gt;Lonboard&#x27;s special sauce is that it uses an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nyhackr.blob.core.windows.net&#x2F;presentations&#x2F;Lonboard-Fast-Interactive-Geospatial-Vector-Data-Visualization-in-Jupyter-and-JavaScript_Kyle-Barron.pdf&quot;&gt;&quot;end-to-end binary data pipeline&quot;&lt;&#x2F;a&gt; skipping GeoJSON entirely.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lonboard-s-innovations&quot;&gt;Lonboard&#x27;s innovations&lt;&#x2F;h2&gt;
&lt;p&gt;There are 3 major innovations to lonboard that make it so powerful:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Lonboard uses &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;geoparquet.org&#x2F;&quot;&gt;&lt;strong&gt;GeoParquet&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; instead of GeoJSON&lt;&#x2F;li&gt;
&lt;li&gt;Data is stored as &lt;strong&gt;GeoArrow&lt;&#x2F;strong&gt; in memory&lt;&#x2F;li&gt;
&lt;li&gt;It uses deck.gl, a GPU powered renderer&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;By relying on GeoParquet and GeoArrow throughout the entire pipeline, lonboard avoids expensive (de)serialization costs every step of the way.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;geoparquet&quot;&gt;GeoParquet&lt;&#x2F;h3&gt;
&lt;p&gt;Lonboard eliminates the text serialization entirely by leaning on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;geoparquet.org&#x2F;&quot;&gt;GeoParquet&lt;&#x2F;a&gt;. GeoParquet is an extension to the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parquet.apache.org&#x2F;&quot;&gt;Parquet&lt;&#x2F;a&gt; file format, which is itself a compressed binary columnar format. Instead of sending a multi-megabyte string across the wire and then parsing it, lonboard sends the GeoParquet file as binary.&lt;&#x2F;p&gt;
&lt;p&gt;The workflow looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;
flowchart LR
A[GeoDataFrame] --&amp;gt; B[Write to GeoParquet]
B --&amp;gt; C[Send to the browser]
C --&amp;gt; D[Read with parquet-wasm]
D --&amp;gt; E[Pass directly to deck.gl]
&lt;&#x2F;pre&gt;
&lt;p&gt;According to the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developmentseed.org&#x2F;lonboard&#x2F;latest&#x2F;how-it-works&#x2F;#how-is-it-so-fast&quot;&gt;lonboard docs&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Saving GeoArrow to Parquet was 135x faster than saving a GeoDataFrame to GeoJSON. The resulting file was 26x smaller. And parsing the Parquet to Arrow on the frontend was 5.6x faster than &lt;code&gt;JSON.parse&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The format yields speedups orders of magnitude faster than we could achieve with GeoJSON.
This change makes it possible to ship datasets to the browser that were previously off the table.&lt;&#x2F;p&gt;
&lt;div class=&quot;callout-tip&quot;&gt;
  Watch Kyle Barron&#x27;s (author of lonboard) great talk on &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=KWQMa-y8CNg&quot;&gt;Why GeoParquet Matters&lt;&#x2F;a&gt;.
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;geoarrow&quot;&gt;GeoArrow&lt;&#x2F;h2&gt;
&lt;p&gt;The real benefit that we get from GeoParqeut comes from the usage of GeoArrow!
GeoArrow is an extension of the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arrow.apache.org&#x2F;&quot;&gt;Apache Arrow&lt;&#x2F;a&gt; (Arrow for short) specification.
Arrow specifies how data should be represented &lt;em&gt;in memory&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Arrow is a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arrow.apache.org&#x2F;docs&#x2F;format&#x2F;Intro.html&quot;&gt;&lt;em&gt;columnar&lt;&#x2F;em&gt; storage format&lt;&#x2F;a&gt;. Simply put, it means that data is stored in columns rather than rows. Doing so make it so that only columns can be accessed without having to sift through every single row of a dataset.&lt;&#x2F;p&gt;
&lt;div class=&quot;flex flex-col items-center gap-2 my-4&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;arrow.apache.org&#x2F;docs&#x2F;_images&#x2F;columnar-diagram_1.svg&quot; class=&quot;w-full sm:w-[70%]&quot;&#x2F;&gt;
  &lt;p class=&quot;text-sm text-muted-foreground text-center&quot;&gt;Diagram from the &lt;a href=&quot;https:&#x2F;&#x2F;arrow.apache.org&#x2F;docs&#x2F;format&#x2F;Intro.html&quot; class=&quot;underline hover:text-foreground&quot;&gt;Apache Arrow columnar format specification&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Arrow is a &lt;em&gt;standard&lt;&#x2F;em&gt; and not a library. The benefit of this is that Arrow data can be sent between tools &#x2F; languages &#x2F; runtimes without incurring a (de)serialization cost enabling &quot;seamless&quot; interoperability.&lt;&#x2F;p&gt;
&lt;div class=&quot;flex flex-col items-center gap-2 my-4&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;josiah.rs&#x2F;posts&#x2F;cross-language-ds&#x2F;images&#x2F;paste-8.png&quot;  class=&quot;w-full sm:w-[70%]&quot;&#x2F;&gt;
  &lt;p class=&quot;text-sm text-muted-foreground text-center&quot;&gt;Diagram from &lt;a href=&quot;https:&#x2F;&#x2F;josiah.rs&#x2F;posts&#x2F;cross-language-ds&quot; class=&quot;underline hover:text-foreground&quot;&gt;Apache Arrow, Rust, and cross-language data science&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;GeoArrow builds upon the Arrow specification to create a standard for how spatial data shuold be represented in memory. Meaning any tool that can use GeoArrow can use the same data—i.e. GeoArrow from Python can be used by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;geoarrow&#x2F;geoarrow-r&quot;&gt;&lt;code&gt;geoarrow-r&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arrow.apache.org&#x2F;nanoarrow&#x2F;latest&#x2F;r&#x2F;&quot;&gt;&lt;code&gt;{nanoarrow}&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;geoarrow-handshake-geoparquet&quot;&gt;GeoArrow 🤝 GeoParquet&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;geoparquet.org&#x2F;releases&#x2F;v1.1.0&#x2F;&quot;&gt;GeoParquet 1.1&lt;&#x2F;a&gt; release added support for &lt;strong&gt;native&lt;&#x2F;strong&gt; GeoArrow encoding. When geometry is stored in native GeoArrow format inside a Parquet file, the browser can read the file and access the geometry directly without converting the data &lt;em&gt;to&lt;&#x2F;em&gt; GeoArrow.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;deck.gl&#x2F;&quot;&gt;deck.gl&lt;&#x2F;a&gt;, the tool used by lonboard, has an extension that supports the use of native GeoArrow.
Thus, coupling GeoParquet and native GeoArrow enables transferring data from disk to GPU without any intermediate format.&lt;&#x2F;p&gt;
&lt;p&gt;This is what &quot;zero-cost serialization&quot; means in practice—the format you store the data in is the same format the renderer consumes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deckgl-and-geoarrow&quot;&gt;DeckGL and GeoArrow&lt;&#x2F;h2&gt;
&lt;p&gt;The piece that ties this all together is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;geoarrow&#x2F;deck.gl-geoarrow&quot;&gt;&lt;code&gt;@geoarrow&#x2F;deck.gl-geoarrow&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. It is a small &quot;glue library&quot; that lets deck.gl consume GeoArrow data. &lt;code&gt;deck.gl-geoarrow&lt;&#x2F;code&gt; takes an Arrow Table from JavaScript and copies it directly to the GPU. deck.gl supports GeoArrow natively (specifically the interleaved coordinate layout).&lt;&#x2F;p&gt;
&lt;p&gt;The deck.gl-geoarrow demo renders 3.2 million points with a &lt;code&gt;GeoArrowScatterplotLayer&lt;&#x2F;code&gt;. For context, that is roughly 150x the size of the King County dataset our demo app ships.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gpu-rendering&quot;&gt;GPU rendering&lt;&#x2F;h2&gt;
&lt;p&gt;The other half of the story is where rendering actually happens. Leaflet renders using the CPU. Every marker and every polygon is drawn using the CPU which works fine for a few hundred features, but it does not scale nearly as well as the GPU.&lt;&#x2F;p&gt;
&lt;p&gt;Why would one use the CPU to do graphics when modern GPUs are incredibly powerful? Modern enhancements to GPUs have made using them to render graphics very, very fast and very efficient.&lt;&#x2F;p&gt;
&lt;p&gt;Lonboard renders via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;deck.gl&#x2F;&quot;&gt;deck.gl&lt;&#x2F;a&gt;, which runs on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.khronos.org&#x2F;webgl&#x2F;&quot;&gt;WebGL&lt;&#x2F;a&gt; and pushes geometry to the GPU.&lt;&#x2F;p&gt;
&lt;p&gt;The remaining piece is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kylebarron&#x2F;parquet-wasm&quot;&gt;parquet-wasm&lt;&#x2F;a&gt;, which reads the Parquet file in the browser via WebAssembly. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;webassembly.org&#x2F;&quot;&gt;WebAssembly&lt;&#x2F;a&gt; (Wasm) is an instruction format that lets you run code written in languages like Rust or C++ inside the browser at near-native speed. &lt;code&gt;parquet-wasm&lt;&#x2F;code&gt; uses WASM to read Parquet files directly in the browser, so the data never has to be converted out of its native format before reaching deck.gl.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-the-shiny-app&quot;&gt;Building the Shiny app&lt;&#x2F;h2&gt;
&lt;p&gt;Since lonboard integrates with Jupyter Widgets via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;anywidget.dev&#x2F;&quot;&gt;anywidget&lt;&#x2F;a&gt;, it works in Shiny for Python through &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;posit-dev&#x2F;py-shinywidgets&quot;&gt;shinywidgets&lt;&#x2F;a&gt; with minimal glue code. The &lt;code&gt;@render_widget&lt;&#x2F;code&gt; decorator drops the map directly into the UI:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E0DEF4; background-color: #191724;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;render_widget&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt; map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; Map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C4A7E7;font-style: italic;&quot;&gt;        layers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;layer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C4A7E7;font-style: italic;&quot;&gt;        view_state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt;            &amp;quot;longitude&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;122.2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt;            &amp;quot;latitude&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt; 47.5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt;            &amp;quot;zoom&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt; 9&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;        },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C4A7E7;font-style: italic;&quot;&gt;        show_side_panel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;False&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;efficient-filtering&quot;&gt;Efficient Filtering&lt;&#x2F;h3&gt;
&lt;p&gt;Transferring data is where much of the bottleneck comes from.
When adding interactivity, it&#x27;s critical that data only be sent &lt;em&gt;once&lt;&#x2F;em&gt;.
Recreating the widget sends the full dataset from Python to the browser.
Lonboard&#x27;s &lt;code&gt;DataFilterExtension&lt;&#x2F;code&gt; solves this by moving filtering to the GPU.
It sets the filter range inside of the browser and, critically, does not re-render the widget with the subsetted data.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E0DEF4; background-color: #191724;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;reactive&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;effect&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt; _update_filter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    layer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;filter_range&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;        [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;price_min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;(),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt; input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;price_max&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;()],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;        [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;BED_CHOICES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;bedrooms&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;()],&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; BEDS_MAX&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;        [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt;BATH_CHOICES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;bathrooms&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;()],&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; BATHS_MAX&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;        [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqft_min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;(),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EB6F92;font-style: italic;&quot;&gt; input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sqft_max&lt;&#x2F;span&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;()],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #908CAA;&quot;&gt;    ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This ensures that the map doesn&#x27;t re-render.&lt;&#x2F;p&gt;
&lt;!-- gif: filtering in action --&gt;
&lt;h2 id=&quot;the-broader-ecosystem&quot;&gt;The broader ecosystem&lt;&#x2F;h2&gt;
&lt;p&gt;This approach is not Python-only. In the R ecosystem, the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;r-consortium.org&#x2F;all-projects&#x2F;2025-group-2.html#modernizing-rs-web-mapping-capabilities&quot;&gt;r-consortium has funded active work&lt;&#x2F;a&gt; on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;r-spatial&#x2F;geoarrowWidget&quot;&gt;geoarrowWidget&lt;&#x2F;a&gt; to bring the same GeoArrow-native pipeline to R.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;walker-data.com&#x2F;mapgl&#x2F;&quot;&gt;mapgl R package&lt;&#x2F;a&gt; is already fast and highly effective, though it &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;walkerke&#x2F;mapgl&#x2F;discussions&#x2F;71&quot;&gt;currently still uses GeoJSON&lt;&#x2F;a&gt; for data transfer. The direction of travel is clear.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deploy-to-ricochet&quot;&gt;Deploy to Ricochet&lt;&#x2F;h2&gt;
&lt;p&gt;Install the CLI, add the server, and deploy:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E0DEF4; background-color: #191724;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; -fsSL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;ricochet-rs&#x2F;cli&#x2F;main&#x2F;install.sh&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt; sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;ricochet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; servers add try https:&#x2F;&#x2F;try.ricochet.rs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;ricochet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; login&lt;&#x2F;span&gt;&lt;span style=&quot;color: #31748F;&quot;&gt; -S&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; try&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBBCBA;&quot;&gt;ricochet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F6C177;&quot;&gt; deploy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;further-reading&quot;&gt;Further reading&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developmentseed.org&#x2F;lonboard&#x2F;latest&#x2F;how-it-works&#x2F;#how-is-it-so-fast&quot;&gt;How lonboard works&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=KWQMa-y8CNg&quot;&gt;Why GeoParquet (Kyle Barron talk)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;geoparquet.org&#x2F;releases&#x2F;v1.1.0&#x2F;&quot;&gt;GeoParquet 1.1 spec&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kylebarron&#x2F;parquet-wasm&quot;&gt;parquet-wasm&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;r-spatial&#x2F;geoarrowWidget&quot;&gt;geoarrowWidget for R&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.djnavarro.net&#x2F;posts&#x2F;2021-11-19_starting-apache-arrow-in-r&#x2F;#overview-of-arrow&quot;&gt;Getting started with Apache Arrow&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;josiah.rs&#x2F;posts&#x2F;cross-language-ds&#x2F;&quot;&gt;Apache Arrow, Rust, and cross-langauge data science&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
</feed>
