<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ryousuke Wayama</title>
    <description>The latest articles on DEV Community by Ryousuke Wayama (@ryousuke_wayama_f134a69e4).</description>
    <link>https://dev.to/ryousuke_wayama_f134a69e4</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3791032%2Fd06f2d20-b478-4714-824e-a56920335769.jpg</url>
      <title>DEV Community: Ryousuke Wayama</title>
      <link>https://dev.to/ryousuke_wayama_f134a69e4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ryousuke_wayama_f134a69e4"/>
    <language>en</language>
    <item>
      <title>How We Animated GraphCast’s Global Weather Predictions in Real-Time on an iPhone Using Cesium &amp; WebGL Shaders</title>
      <dc:creator>Ryousuke Wayama</dc:creator>
      <pubDate>Tue, 03 Mar 2026 00:51:06 +0000</pubDate>
      <link>https://dev.to/ryousuke_wayama_f134a69e4/how-we-animated-graphcasts-global-weather-predictions-in-real-time-on-an-iphone-using-cesium--3o0m</link>
      <guid>https://dev.to/ryousuke_wayama_f134a69e4/how-we-animated-graphcasts-global-weather-predictions-in-real-time-on-an-iphone-using-cesium--3o0m</guid>
      <description>&lt;p&gt;Imagine taking Google DeepMind’s GraphCast—a state-of-the-art AI weather model—and visualizing its global, high-resolution predictions smoothly on a mobile browser. Sounds like a fast track to melting your iPhone's GPU, right?&lt;/p&gt;

&lt;p&gt;Usually, processing and rendering massive multi-dimensional spatial data (like global wind patterns, temperature, and pressure across multiple time steps) requires hefty desktop hardware. When you try to render that kind of heavy data onto a 3D web globe on a mobile device, you typically hit a massive wall: memory limits, terrible framerates, and inevitable browser crashes.&lt;/p&gt;

&lt;p&gt;But at the R&amp;amp;D department of &lt;strong&gt;Northern System Service&lt;/strong&gt;—where we regularly build robust GIS systems and visualize spatial data for Japanese government agencies like JAXA and JAMSTEC—we love pushing browser limits.&lt;/p&gt;

&lt;p&gt;We didn't want a clunky, pre-rendered video. We wanted real-time, interactive 3D rendering on a smartphone. By rethinking our data pipeline and offloading the heavy lifting to the GPU using &lt;strong&gt;Cesium&lt;/strong&gt; and &lt;strong&gt;custom WebGL Shaders (GLSL)&lt;/strong&gt;, we managed to render GraphCast's global predictions natively in the mobile browser at a silky-smooth frame rate.&lt;/p&gt;

&lt;p&gt;Here is how we hacked the pipeline, bypassed the CPU bottlenecks, and turned a mobile phone into a real-time global weather simulator.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Why": Translating Weather Jargon into 3D Reality
&lt;/h2&gt;

&lt;p&gt;You always hear meteorologists talk about the "eye of a typhoon" or a "pressure trough." But let’s be honest—looking at standard 2D contour lines means almost nothing to the average person.&lt;/p&gt;

&lt;p&gt;I thought: &lt;em&gt;What if we could visualize barometric pressure as physical 3D terrain and animate it?&lt;/em&gt; If a pressure drop actually looked like a massive crater or a deep valley on a 3D globe, anyone could instantly and intuitively grasp the weather dynamics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt #1: Cesium Terrain (And The Bottleneck)
&lt;/h2&gt;

&lt;p&gt;My first naive approach was to use Cesium's native Terrain provider. It’s fantastic for rendering static mountains, but animating a dynamic, global, high-resolution 3D mesh across multiple time-steps (using GraphCast's time-series predictions) was a disaster.&lt;/p&gt;

&lt;p&gt;The CPU was completely overwhelmed trying to update the geometry frame-by-frame. The browser choked, and achieving a smooth, real-time animation—especially on a mobile device like an iPhone—was completely out of the question. I needed a radically different approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pivot: Custom WebGL Shaders to the Rescue
&lt;/h2&gt;

&lt;p&gt;If the CPU can't handle updating the 3D geometry, why not force the GPU to do all the heavy lifting? I pivoted to writing custom WebGL shaders.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hack: Baking Pixel Indices into a Custom glTF
&lt;/h3&gt;

&lt;p&gt;GraphCast outputs massive global grid data. To animate the globe based on this data, every single point on our 3D model needed to know exactly which pixel to read from the texture.&lt;/p&gt;

&lt;p&gt;Instead of doing expensive lookups on the fly, I built a Python pipeline using &lt;code&gt;GDAL&lt;/code&gt;, &lt;code&gt;pymap3d&lt;/code&gt;, and &lt;code&gt;pygltflib&lt;/code&gt; to generate a custom glTF model. I converted WGS84 coordinates into Earth-Centered, Earth-Fixed (ECEF) coordinates.&lt;/p&gt;

&lt;p&gt;But here is the secret sauce: &lt;strong&gt;I injected the GeoTIFF pixel index directly into the glTF vertex attributes.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pymap3d&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pm&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pygltflib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;xyz_to_ecef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# WGS84 to ECEF conversion
&lt;/span&gt;    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geodetic2ecef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;

&lt;span class="c1"&gt;# ... generating coordinates ...
&lt;/span&gt;
&lt;span class="c1"&gt;# Create the custom glTF with baked attributes
&lt;/span&gt;&lt;span class="n"&gt;gltf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GLTF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="n"&gt;accessors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Accessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;bufferView&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;componentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# 3D Model Vertex Coordinates (ECEF)
&lt;/span&gt;            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VEC3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Accessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;bufferView&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;componentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lonlat_list&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# WGS84 Coordinates
&lt;/span&gt;            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VEC2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lonlat_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lonlat_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Accessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;bufferView&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;componentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UNSIGNED_INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx_list&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# The magical GeoTIFF pixel index!
&lt;/span&gt;            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pygltflib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VEC2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;idx_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;idx_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Displacement Mapping on the GPU
&lt;/h3&gt;

&lt;p&gt;With the index baked into the geometry, the GPU does the rest. I wrote a custom Vertex Shader based on Cesium's Point Cloud architecture.&lt;/p&gt;

&lt;p&gt;For every frame, the shader grabs the baked index (&lt;code&gt;vsInput.attributes.idx&lt;/code&gt;), calculates the exact UV position, fetches the barometric pressure from the GeoTIFF texture, calculates the new height, and updates the ECEF coordinates instantly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;vertexMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VertexInput&lt;/span&gt; &lt;span class="n"&gt;vsInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;inout&lt;/span&gt; &lt;span class="n"&gt;czm_modelVertexOutput&lt;/span&gt; &lt;span class="n"&gt;vsOutput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Retrieve WGS84 coords and GeoTIFF index from glTF attributes&lt;/span&gt;
    &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;lonlat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vsInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longlat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;geotiff_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vsInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculate UV position (Global grid is 1440x721)&lt;/span&gt;
    &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;uv_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geotiff_idx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1439&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geotiff_idx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;719&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch the pressure value from the GeoTIFF texture&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;zval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_tex_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uv_pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

    &lt;span class="c1"&gt;// Calculate new Z height based on pressure&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;newz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;zval&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;zmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;heightBuf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;newXyz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geodeticToECEF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lonlat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lonlat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

    &lt;span class="c1"&gt;// Update vertex position&lt;/span&gt;
    &lt;span class="n"&gt;vsOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positionMC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newXyz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="n"&gt;vsOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positionMC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newXyz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;vsOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positionMC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newXyz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;vsOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make it visually intuitive, I wrote a Fragment Shader to colorize the points dynamically. By passing the same texture, we calculate a topographical color map right on the GPU.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="nf"&gt;get_cs_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;uv_pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Topographical color calculations based on surrounding pixels&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cs_map&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dem_color&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;contour&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;fragmentMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FragmentInput&lt;/span&gt; &lt;span class="n"&gt;fsInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;inout&lt;/span&gt; &lt;span class="n"&gt;czm_modelMaterial&lt;/span&gt; &lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;geotiff_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fsInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;uv_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geotiff_idx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1439&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geotiff_idx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;719&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply dynamic topographical coloring&lt;/span&gt;
    &lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diffuse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_cs_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uv_pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Overcoming the Mobile OOM Beast
&lt;/h2&gt;

&lt;p&gt;Everything was looking great until I tried to load the time-series data. GraphCast gave me a 40-band GeoTIFF (representing 10 days at 6-hour intervals).&lt;/p&gt;

&lt;p&gt;I tried to load the entire multi-band GeoTIFF at once using &lt;code&gt;geotiff.js&lt;/code&gt; in the browser. &lt;em&gt;Immediate crash.&lt;/em&gt; The mobile browser threw an Out-of-Memory (OOM) error.&lt;/p&gt;

&lt;p&gt;The workaround? I split the 40-band beast into 40 individual single-band GeoTIFFs. By pre-loading these lightweight images sequentially into the browser memory, the crashes stopped entirely. When the user scrubs the timeline slider, the shader simply swaps out which pre-loaded texture it samples from. It runs flawlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leveling Up: From Point Cloud to Solid Mesh
&lt;/h2&gt;

&lt;p&gt;While the animated point cloud was incredibly fast, zooming in revealed the gaps between points. I wanted a solid surface.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw41gn4ovrdk6db54jnv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw41gn4ovrdk6db54jnv1.png" alt="zooming in revealed the gaps between points" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Could this same shader magic work on a solid mesh instead of just points? Absolutely.&lt;/p&gt;

&lt;p&gt;I went back to Python and used the &lt;code&gt;open3d&lt;/code&gt; library to reconstruct a continuous mesh from the point cloud using the Ball Pivoting Algorithm. I then appended these triangle indices to my custom glTF.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;open3d&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;o3d&lt;/span&gt;

&lt;span class="c1"&gt;# Estimate normals for the point cloud
&lt;/span&gt;&lt;span class="n"&gt;ptCloud&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o3d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PointCloud&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ptCloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o3d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Vector3dVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points_ecef_mesh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ptCloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;estimate_normals&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ptCloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orient_normals_consistent_tangent_plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Reconstruct the mesh using Ball Pivoting Algorithm
&lt;/span&gt;&lt;span class="n"&gt;distances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ptCloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute_nearest_neighbor_distance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;avg_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;avg_dist&lt;/span&gt;   
&lt;span class="n"&gt;radii&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;recMeshBPA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o3d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TriangleMesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_from_point_cloud_ball_pivoting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ptCloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o3d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DoubleVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radii&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Add triangle polygons to the glTF
&lt;/span&gt;&lt;span class="n"&gt;triangles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recMeshBPA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triangles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3D point cloud&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq95jz2keg7ihf71s354b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq95jz2keg7ihf71s354b.gif" alt="3D point cloud" width="720" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;reconstructed mesh&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2w5eo7ozqfmf14zdyl2q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2w5eo7ozqfmf14zdyl2q.gif" alt="reconstructed mesh" width="760" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By swapping the point cloud glTF for this new mesh glTF—&lt;strong&gt;without changing a single line of the custom shader&lt;/strong&gt;—the result was a fully dynamic, solid, breathing 3D globe. You can zoom right into the "eye" of a typhoon and see the pressure gradients as a smooth, colored crater.&lt;/p&gt;




&lt;p&gt;When I started this project, I fully expected my iPhone to overheat enough to baked my morning eggs on the screen. But as it turns out, the only thing we ended up &lt;em&gt;baking&lt;/em&gt; were the GeoTIFF indices directly into the glTF attributes.&lt;/p&gt;

&lt;p&gt;My phone stayed perfectly cool, completely ruining my breakfast plans—but we got a buttery-smooth 60 FPS instead. I’d say that’s a fair trade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e41jfowarzqn2hbq63l.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e41jfowarzqn2hbq63l.gif" alt="Yes, this is running buttery-smooth on a 2022 iPhone SE. Too old, you say? Hey, give me a break—it’s the absolute perfect size for watching YouTube in bed!" width="426" height="240"&gt;&lt;/a&gt;&lt;br&gt;
(Yes, this is running buttery-smooth on a 2022 iPhone SE. Too old, you say? Hey, give me a break—it’s the absolute perfect size for watching YouTube in bed!)&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Pushing the WebGL Envelope
&lt;/h2&gt;

&lt;p&gt;Visualizing complex, global-scale AI predictions like GraphCast doesn't have to melt your CPU. By rethinking the data pipeline and offloading the topological calculations to a custom WebGL shader, we turned a sluggish, memory-crashing process into an interactive 3D globe running natively on an iPhone.&lt;/p&gt;

&lt;p&gt;This shader hack isn't just limited to weather forecasting. This architecture can be applied to almost any massive spatial dataset, such as deep-sea oceanography data or real-time disaster prevention monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hungry to try it out yourself?&lt;/strong&gt;&lt;br&gt;
Don't worry, the complete "breakfast recipe" we just discussed is waiting for you right here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/makinux/GridVizA" rel="noopener noreferrer"&gt;breakfast recipe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grab your smartphone, and let's bake and eat a global 3D model together! Bon appétit!&lt;/p&gt;




&lt;h3&gt;
  
  
  Looking for a Team to Solve Your Spatial Data Nightmares? Let’s Talk.
&lt;/h3&gt;

&lt;p&gt;I am a part of the R&amp;amp;D team at &lt;strong&gt;Northern System Service&lt;/strong&gt;, based in Japan. We specialize in solving "impossible" spatial data and 3D visualization problems.&lt;/p&gt;

&lt;p&gt;We build robust systems for national-level projects. Our track record includes developing 3D data explorers for &lt;strong&gt;&lt;a href="https://global.jaxa.jp/" rel="noopener noreferrer"&gt;JAXA&lt;/a&gt;&lt;/strong&gt; (visualizing asteroid data from the Hayabusa2 space probe) and &lt;a href="https://www.jstage.jst.go.jp/article/geoinformatics/33/2/33_33/_article/-char/ja/" rel="noopener noreferrer"&gt;advanced oceanographic visualization systems&lt;/a&gt; for &lt;strong&gt;&lt;a href="https://www.jamstec.go.jp/e/" rel="noopener noreferrer"&gt;JAMSTEC&lt;/a&gt;&lt;/strong&gt;. Furthermore, our R&amp;amp;D pipeline is highly augmented—we actively utilize AI agents (like Claude Code) for autonomous development to deploy complex WebGL/Cesium solutions faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are currently expanding our reach and taking on global contracting projects.&lt;/strong&gt; If your team is struggling with WebGL performance bottlenecks, needs to visualize massive AI-generated spatial datasets, or wants to build next-generation 3D globes using Cesium, we can help.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Reach out to us for consulting or contract development:&lt;/strong&gt; &lt;a href="https://www.nssv.co.jp/randd/en/rdcontact/" rel="noopener noreferrer"&gt;Northern system service Co.,Ltd. R&amp;amp;D contact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Follow me for more WebGL/GIS hacks:&lt;/strong&gt; [&lt;br&gt;
&lt;a href="https://x.com/wayama_ryousuke" rel="noopener noreferrer"&gt;X&lt;/a&gt; / &lt;a href="https://github.com/makinux" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; ]&lt;/p&gt;

&lt;p&gt;Let’s build something visually mind-blowing together.&lt;/p&gt;




</description>
      <category>cesium</category>
      <category>graphcast</category>
      <category>webgl</category>
      <category>glsl</category>
    </item>
  </channel>
</rss>
