[{"data":1,"prerenderedAt":2291},["ShallowReactive",2],{"navigation":3,"work-page":47,"work":57,"\u002Fwork\u002Fdmap-cache":2221,"\u002Fwork\u002Fdmap-cache-surround":2289},[4,22],{"title":5,"path":6,"stem":7,"children":8,"page":21},"Blog","\u002Fblog","blog",[9,13,17],{"title":10,"path":11,"stem":12},"Anki Baby Steps","\u002Fblog\u002Fanki-baby-steps","blog\u002Fanki-baby-steps",{"title":14,"path":15,"stem":16},"GSoC 2026: a Hopeful Rejection","\u002Fblog\u002Fgsoc-2026-a-hopeful-rejection","blog\u002Fgsoc-2026-a-hopeful-rejection",{"title":18,"path":19,"stem":20},"Play vs Prove: CFU comparison","\u002Fblog\u002Fplay-vs-prove-cfu-comparison","blog\u002Fplay-vs-prove-cfu-comparison",false,{"title":23,"path":24,"stem":25,"children":26,"page":21},"Work","\u002Fwork","work",[27,31,35,39,43],{"title":28,"path":29,"stem":30},"D-Map Cache","\u002Fwork\u002Fdmap-cache","work\u002Fdmap-cache",{"title":32,"path":33,"stem":34},"Edge Relax","\u002Fwork\u002Fedge-relax","work\u002Fedge-relax",{"title":36,"path":37,"stem":38},"Focal Grid","\u002Fwork\u002Ffocal-grid","work\u002Ffocal-grid",{"title":40,"path":41,"stem":42},"Pico Router","\u002Fwork\u002Fpico-router","work\u002Fpico-router",{"title":44,"path":45,"stem":46},"Valhalla","\u002Fwork\u002Fvalhalla","work\u002Fvalhalla",{"id":48,"title":49,"body":50,"description":51,"extension":52,"links":50,"meta":53,"navigation":54,"path":24,"seo":55,"stem":25,"__hash__":56},"pages\u002Fwork.yml","Some stuff I've done",null,"My work varies from client and open source projects to more educational-oriented personal endeavors. I'm interested in high performance and constrained C++ systems applications but my work also features full-stack JavaScript work.","yml",{},true,{"title":49,"description":51},"sSZ4FKl-SGCKEwkWLhkBmU_-nVlgEbR9XM-BGjRL7hE",[58,190,216,240,420],{"id":59,"title":28,"body":60,"color":175,"date":176,"description":177,"extension":178,"image":179,"meta":180,"navigation":54,"oneliner":181,"path":29,"seo":182,"stem":30,"tags":183,"url":188,"__hash__":189},"work\u002Fwork\u002Fdmap-cache.md",{"type":61,"value":62,"toc":171},"minimark",[63,68,72,76,86,91,122,126,161,167],[64,65,67],"h2",{"id":66},"whats-the-point-of-adding-such-a-simple-cache","What's the point of adding such a simple cache?",[69,70,71],"p",{},"The cache provides a simplified model of a cached memory hierarchy, allowing experiments with temporal and spatial locality and the performance impact of cache-aware versus cache-unaware memory-access patterns.",[64,73,75],{"id":74},"helpful-links","Helpful Links",[77,78,83],"card",{"icon":79,"target":80,"title":81,"to":82},"i-simple-icons-github","_blank","Merged PR","https:\u002F\u002Fgithub.com\u002Fnuxt-ui-templates\u002Fdocs",[69,84,85],{},"Link to final merged Pull Request on GitHub",[87,88,90],"h1",{"id":89},"draw-diagram-of-current-soc-setup","Draw Diagram of Current SoC setup!!",[92,93,99],"pre",{"className":94,"code":95,"filename":96,"language":97,"meta":98,"style":98},"language-plaintext shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","           Tag           Index\n┌───────────┴───────────┐┌─┴┐\n00000000000000000000000000000000 ──── Unused\n","Address Slicing (Maybe Replace With Hand Drawing)","plaintext","",[100,101,102,110,116],"code",{"__ignoreMap":98},[103,104,107],"span",{"class":105,"line":106},"line",1,[103,108,109],{},"           Tag           Index\n",[103,111,113],{"class":105,"line":112},2,[103,114,115],{},"┌───────────┴───────────┐┌─┴┐\n",[103,117,119],{"class":105,"line":118},3,[103,120,121],{},"00000000000000000000000000000000 ──── Unused\n",[87,123,125],{"id":124},"explain-fsm","Explain FSM!!",[127,128,129,142],"table",{},[130,131,132],"thead",{},[133,134,135,139],"tr",{},[136,137,138],"th",{},"Original",[136,140,141],{},"Mine",[143,144,145],"tbody",{},[133,146,147,155],{},[148,149,150],"td",{},[151,152],"img",{"alt":153,"src":154},"reference demo gif","\u002Fcontent\u002Fd-map-cache-gif-2.gif",[148,156,157],{},[151,158],{"alt":159,"src":160},"buggy demo gif","\u002Fcontent\u002Fd-map-cache-gif-1.gif",[69,162,163],{},[151,164],{"alt":165,"src":166},"signal traces","\u002Fcontent\u002Fd-map-cache-screenshot-1.png",[168,169,170],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":98,"searchDepth":112,"depth":112,"links":172},[173,174],{"id":66,"depth":112,"text":67},{"id":74,"depth":112,"text":75},"#8c1826","2026-08-01","Designed and developed a simple Direct Mapped CPU cache for the CFU Proving Ground RISC-V implementation.","md","https:\u002F\u002Fp7i.vogel.de\u002Fwcms\u002F0a\u002F43\u002F0a436a696528c4a9675af94c2e79d960\u002F0126825714v1.jpeg",{},"RISC-V Cache",{"title":28,"description":177},[184,185,186,187],"RISC-V","Verilog","FPGA","Architecture","#","vrKUS3VLrFC7YJ3QTEYMLSKtJpAizLBjFcqEaSTJpq0",{"id":191,"title":32,"body":192,"color":204,"date":205,"description":206,"extension":178,"image":207,"meta":208,"navigation":54,"oneliner":209,"path":33,"seo":210,"stem":34,"tags":211,"url":188,"__hash__":215},"work\u002Fwork\u002Fedge-relax.md",{"type":61,"value":193,"toc":202},[194],[92,195,200],{"className":196,"code":198,"language":199},[197],"language-text","Baseline C implementation\n↓\nprofile\n↓\nidentify hotspot\n↓\ndesign custom RISC-V instruction\n↓\nimplement in CFU Playground HDL\n↓\nadd opcode\n↓\ncall through intrinsic\n↓\nbenchmark\n","text",[100,201,198],{"__ignoreMap":98},{"title":98,"searchDepth":112,"depth":112,"links":203},[],"#FF6C3A","2026-07-01","Designed and developed the user interface for an indie music streaming service, focusing on discovery features and creating a unique, engaging listening experience using custom audio visualizations.","https:\u002F\u002Fwww.idtools.com.au\u002Fsite_2021\u002Fwp-content\u002Fuploads\u002F2021\u002F05\u002F3D-network-plots-python-mplot3d-toolkit.png",{},"FPGA Accelerator",{"title":32,"description":206},[212,213,214],"UI Design","Front-End Dev","Animation","x_qI6qwzUBxuSHSFYgxZbsD4h3Lpeu2ca96ZPzXAUng",{"id":217,"title":40,"body":218,"color":227,"date":228,"description":229,"extension":178,"image":230,"meta":231,"navigation":54,"oneliner":232,"path":41,"seo":233,"stem":42,"tags":234,"url":188,"__hash__":239},"work\u002Fwork\u002Fpico-router.md",{"type":61,"value":219,"toc":224},[220],[64,221,223],{"id":222},"tldr","TL;DR",{"title":98,"searchDepth":112,"depth":112,"links":225},[226],{"id":222,"depth":112,"text":223},"#FF4B84","2026-05-01","Creator and maintainer of an open source navigation firmware project. The main goal being autonomous GPS routing for Raspberry Pi Pico 2 hardware targets using OpenStreetMap data.","https:\u002F\u002Fstatic0.xdaimages.com\u002Fwordpress\u002Fwp-content\u002Fuploads\u002F2024\u002F11\u002Fraspberry-pi-pico-2-w-featured.png?w=1200&h=628&fit=crop",{},"Navigation Firmware",{"title":40,"description":229},[235,236,237,238],"Embedded Systems","C++","Python","OpenStreetMap","gTT3JLfPHUqtAR_JrcHHtzAJDNtPz9my8qAZlesH8f8",{"id":241,"title":44,"body":242,"color":408,"date":409,"description":410,"extension":178,"image":411,"meta":412,"navigation":54,"oneliner":413,"path":45,"seo":414,"stem":46,"tags":415,"url":188,"__hash__":419},"work\u002Fwork\u002Fvalhalla.md",{"type":61,"value":243,"toc":386},[244,249,253,256,259,263,270,274,277,281,284,286,289,317,320,322,326,329,331,334,350,353,355,359,362,364,367,381,384],[245,246],"table-of-contents",{":title":247,":max-depth":248},"Index","2",[64,250,252],{"id":251},"segfault-bug-fix","Segfault Bug Fix",[254,255,223],"h3",{"id":222},[69,257,258],{},"Add TL;DR...",[254,260,262],{"id":261},"relevant-links","Relevant Links",[264,265,266],"ul",{},[267,268,269],"li",{},"Nothing yet...",[254,271,273],{"id":272},"breakdown","Breakdown",[69,275,276],{},"Add outcome...",[64,278,280],{"id":279},"python-openlr-module","Python OpenLR Module",[254,282,223],{"id":283},"tldr-1",[69,285,258],{},[254,287,262],{"id":288},"relevant-links-1",[264,290,291,301,309],{},[267,292,293,294],{},"Discussion: ",[295,296,300],"a",{"href":297,"rel":298},"https:\u002F\u002Fgithub.com\u002Fvalhalla\u002Fvalhalla\u002Fdiscussions\u002F6035",[299],"nofollow","Python bindings for OpenLR #6035",[267,302,303,304],{},"Issue: ",[295,305,308],{"href":306,"rel":307},"https:\u002F\u002Fgithub.com\u002Fvalhalla\u002Fvalhalla\u002Fissues\u002F6037",[299],"OpenLR Python bindings #6037",[267,310,311,312],{},"Pull Request: ",[295,313,316],{"href":314,"rel":315},"https:\u002F\u002Fgithub.com\u002Fvalhalla\u002Fvalhalla\u002Fpull\u002F6087",[299],"feat: openlr python bindings #6087",[254,318,273],{"id":319},"breakdown-1",[69,321,276],{},[64,323,325],{"id":324},"python-bindging-restructuring","Python Bindging Restructuring",[254,327,223],{"id":328},"tldr-2",[69,330,258],{},[254,332,262],{"id":333},"relevant-links-2",[264,335,336,343],{},[267,337,293,338],{},[295,339,342],{"href":340,"rel":341},"https:\u002F\u002Fgithub.com\u002Fvalhalla\u002Fvalhalla\u002Fdiscussions\u002F6096#discussioncomment-17225673",[299],"re-design python binding structure #6096",[267,344,311,345],{},[295,346,349],{"href":347,"rel":348},"https:\u002F\u002Fgithub.com\u002Fvalhalla\u002Fvalhalla\u002Fpull\u002F6133",[299],"BREAKING(python): change layout of bindings, no signature change #6133",[254,351,273],{"id":352},"breakdown-2",[69,354,276],{},[64,356,358],{"id":357},"docker-version-logging","Docker Version Logging",[254,360,223],{"id":361},"tldr-3",[69,363,258],{},[254,365,262],{"id":366},"relevant-links-3",[264,368,369,374],{},[267,370,293,371],{},[295,372,342],{"href":340,"rel":373},[299],[267,375,311,376],{},[295,377,380],{"href":378,"rel":379},"https:\u002F\u002Fgithub.com\u002Fvalhalla\u002Fvalhalla\u002Fissues\u002F5721",[299],"Include Valhalla version in Docker container startup logs #5721",[254,382,273],{"id":383},"breakdown-3",[69,385,276],{},{"title":98,"searchDepth":112,"depth":112,"links":387},[388,393,398,403],{"id":251,"depth":112,"text":252,"children":389},[390,391,392],{"id":222,"depth":118,"text":223},{"id":261,"depth":118,"text":262},{"id":272,"depth":118,"text":273},{"id":279,"depth":112,"text":280,"children":394},[395,396,397],{"id":283,"depth":118,"text":223},{"id":288,"depth":118,"text":262},{"id":319,"depth":118,"text":273},{"id":324,"depth":112,"text":325,"children":399},[400,401,402],{"id":328,"depth":118,"text":223},{"id":333,"depth":118,"text":262},{"id":352,"depth":118,"text":273},{"id":357,"depth":112,"text":358,"children":404},[405,406,407],{"id":361,"depth":118,"text":223},{"id":366,"depth":118,"text":262},{"id":383,"depth":118,"text":273},"#64748B","2026-02-01","Contributor to Valhalla is a high-performance routing engine built on OpenStreetMap data. My work focused on Python interoperability, OpenLR support, developer experience, and infrastructure improvements.","https:\u002F\u002Fvalhalla.github.io\u002Fvalhalla\u002Fapi\u002Fimages\u002Fisochrone.png",{},"OSM Routing Engine",{"title":44,"description":410},[416,417,418,236,237],"OSM","GIS","Routing Engine","4C6_czmGkrJIxlL9qD_RzEGWTut1uVYwuy9f-MI1G2E",{"id":421,"title":36,"body":422,"color":2208,"date":2209,"description":2210,"extension":178,"image":2211,"meta":2212,"navigation":54,"oneliner":2213,"path":37,"seo":2214,"stem":38,"tags":2215,"url":188,"__hash__":2220},"work\u002Fwork\u002Ffocal-grid.md",{"type":61,"value":423,"toc":2201},[424,426,429,433,442,446,449,453,461,464,467,470,474,479,486,500,503,506,509,513,516,519,1167,1170,1174,1194,1197,1662,1668,1718,1721,1724,1755,1767,1770,1869,1872,1934,1949,1959,2063,2066,2144,2153,2198],[254,425,223],{"id":222},[69,427,428],{},"Built a full-stack GIS platform for visualizing Dutch energy grid data using Nuxt, Mapbox, Supabase, and PostGIS. Implemented a GeoJSON ingestion pipeline, custom map interactions, and a performant vector tile backend that serves geospatial data directly from PostGIS while keeping Mapbox purely as the rendering engine.",[64,430,432],{"id":431},"mini-demo","Mini Demo",[434,435,436],"callout",{},[69,437,438],{},[151,439],{"alt":440,"src":441},"Focal Grid Demo GIF","content\u002Ffocal-grid-demo.gif",[64,443,445],{"id":444},"background","Background",[69,447,448],{},"While working full-time on freelance full-stack projects got in contact with a client looking to build a GIS-based visualisation platform. The pitch was straightfoward, in service of energy grid buildout in the Netherlands many private providers are looking to efficiently aggregate data in order make decisions on planning, contracts, budgets, etc. The goal was to build a minimum viable web-based prototype that demonstrates the basic desired visualisation and interaction funcionallity to show to stakeholders. Taking on the challenge as a solo developer, this was one of my most impactful freelance projects seeing as it steered me to GIS engineering for the very first time.",[64,450,452],{"id":451},"tech-stack","Tech Stack",[434,454,455],{},[69,456,457],{},[151,458],{"alt":459,"src":460},"Focal Grid Architecture Overview","content\u002Ffocal-grid-architecture.jpg",[69,462,463],{},"The application follows a fairly straightforward full-stack architecture. The frontend is built with Nuxt and Vue, which pair nicely with the Mapbox GL JS SDK while also giving me a framework I'm comfortable moving quickly in. Since almost all application data is user-specific and fetched on demand, there was little reason to lean on SSR beyond the initial page shell.",[69,465,466],{},"On the backend I used Supabase as the primary BaaS solution. Besides hosting a PostGIS-enabled PostgreSQL database, it also provided authentication and Edge Functions for serving vector tiles directly from the database. This let Mapbox handle rendering while keeping the actual geospatial data entirely outside of the Mapbox ecosystem.",[69,468,469],{},"The project itself was deployed on Vercel, with TypeScript used throughout both the frontend and server code. Finally, GSAP was sprinkled in for a handful of subtle interface animations, mostly around menus and transitions without becoming a central part of the application.",[64,471,473],{"id":472},"key-features","Key Features",[475,476,478],"h4",{"id":477},"custom-ui-map-interactions","Custom UI & Map Interactions",[69,480,481,482,485],{},"Perhaps this might be the easiest part to grasp intuitively seeing the ",[295,483,432],{"href":484},"#mini-demo"," above already reveals most of the built functionality. Still, it might be good to list out the available features after concluding my work. The app largely consists of, number one, a interactive map render component, and two, a header component offering different control options. Controls offered by the popover menu include:",[264,487,488,491,494,497],{},[267,489,490],{},"Toggling different energy grid layers on and off which were seperated by their voltage;",[267,492,493],{},"Toggling energy substations connected to each kind of voltage level;",[267,495,496],{},"On-map polygon drawing and deletion for grouping data points geographically;",[267,498,499],{},"Drag-and-drop icons for projecting energy asset placement;",[69,501,502],{},"Furthermore, the top bar additionally includes simple sattelite view and pitched angle toggle buttons.",[69,504,505],{},"When talking about the map itself, the user is able to zoom and inspect a limited geographical area (in this case Amersfoort in the Netherlands) as you might expect from any other map-based web application. On top of that, user can click on substations to dynamically load and view the stations metadata.",[69,507,508],{},"From a technical standpoint, most of the interaction logic lives in small Vue composables that bridge UI state to Mapbox through some SDK-provided hooks. The trickiest part for me was definetily keeping all of this stable across style reloads. Switching between the custom basemap and satellite view tears down and rebuilds Mapbox layers. This forced me to add special hooks to re-sync restore connection layers and persist drawn points in memory before re-injecting them after the style loads.",[475,510,512],{"id":511},"geojson-to-postgis-pipeline","GeoJSON to PostGIS Pipeline",[69,514,515],{},"The data source for the energy grid related data was open data provided by the municipality of Amersfoort. At the time, only data in GeoJSON format was available. This meant that I needed to find a reliable way in which import this data directly into a Postgres table in Supabase while maintaining the usability of the data.",[69,517,518],{},"I landed on the following helper function. Provided with a local file path, desired table name and Supabase client object it automatically loads the GeoJSON into a PostGIS compatible Supabase table.",[520,521,522],"code-collapse",{},[92,523,528],{"className":524,"code":525,"filename":526,"language":527,"meta":98,"style":98},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","async function importGeoJSON(\n  filePath, \n  tableName, \n  supabaseClient\n) {\n  try {\n    const geojson = JSON.parse(fs.readFileSync(filePath, 'utf8'))\n    \n    \u002F\u002F Process in batches to avoid timeouts\n    const batchSize = 1000\n    const features = geojson.features.map(feature => ({\n      geom: feature.geometry,\n      properties: feature.properties || {}\n    }))\n\n    console.log(`Importing ${features.length} features...`)\n    \n    for (let i = 0; i \u003C features.length; i += batchSize) {\n      const batch = features.slice(i, i + batchSize)\n      const { data, error } = await supabaseClient\n        .from(tableName)\n        .insert(batch)\n      \n      if (error) {\n        console.error('Error inserting batch:', error)\n        break\n      }\n      \n      console.log(`Inserted batch ${Math.floor(i\u002FbatchSize) + 1}\u002F${Math.ceil(features.length\u002FbatchSize)}`)\n    }\n    \n    console.log('Import completed!')\n  } catch (error) {\n    console.error('Import failed:', error)\n  }\n}\n","JavaScript","js",[100,529,530,547,560,569,575,584,593,646,652,659,673,709,729,750,758,764,804,809,856,890,917,933,948,954,969,994,1000,1006,1011,1083,1089,1094,1114,1131,1155,1161],{"__ignoreMap":98},[103,531,532,536,539,543],{"class":105,"line":106},[103,533,535],{"class":534},"spNyl","async",[103,537,538],{"class":534}," function",[103,540,542],{"class":541},"s2Zo4"," importGeoJSON",[103,544,546],{"class":545},"sMK4o","(\n",[103,548,549,553,556],{"class":105,"line":112},[103,550,552],{"class":551},"sHdIc","  filePath",[103,554,555],{"class":545},",",[103,557,559],{"class":558},"sTEyZ"," \n",[103,561,562,565,567],{"class":105,"line":118},[103,563,564],{"class":551},"  tableName",[103,566,555],{"class":545},[103,568,559],{"class":558},[103,570,572],{"class":105,"line":571},4,[103,573,574],{"class":551},"  supabaseClient\n",[103,576,578,581],{"class":105,"line":577},5,[103,579,580],{"class":545},")",[103,582,583],{"class":545}," {\n",[103,585,587,591],{"class":105,"line":586},6,[103,588,590],{"class":589},"s7zQu","  try",[103,592,583],{"class":545},[103,594,596,599,602,605,608,611,614,618,621,623,626,628,631,633,636,640,643],{"class":105,"line":595},7,[103,597,598],{"class":534},"    const",[103,600,601],{"class":558}," geojson",[103,603,604],{"class":545}," =",[103,606,607],{"class":558}," JSON",[103,609,610],{"class":545},".",[103,612,613],{"class":541},"parse",[103,615,617],{"class":616},"swJcz","(",[103,619,620],{"class":558},"fs",[103,622,610],{"class":545},[103,624,625],{"class":541},"readFileSync",[103,627,617],{"class":616},[103,629,630],{"class":558},"filePath",[103,632,555],{"class":545},[103,634,635],{"class":545}," '",[103,637,639],{"class":638},"sfazB","utf8",[103,641,642],{"class":545},"'",[103,644,645],{"class":616},"))\n",[103,647,649],{"class":105,"line":648},8,[103,650,651],{"class":616},"    \n",[103,653,655],{"class":105,"line":654},9,[103,656,658],{"class":657},"sHwdD","    \u002F\u002F Process in batches to avoid timeouts\n",[103,660,662,664,667,669],{"class":105,"line":661},10,[103,663,598],{"class":534},[103,665,666],{"class":558}," batchSize",[103,668,604],{"class":545},[103,670,672],{"class":671},"sbssI"," 1000\n",[103,674,676,678,681,683,685,687,690,692,695,697,700,703,706],{"class":105,"line":675},11,[103,677,598],{"class":534},[103,679,680],{"class":558}," features",[103,682,604],{"class":545},[103,684,601],{"class":558},[103,686,610],{"class":545},[103,688,689],{"class":558},"features",[103,691,610],{"class":545},[103,693,694],{"class":541},"map",[103,696,617],{"class":616},[103,698,699],{"class":551},"feature",[103,701,702],{"class":534}," =>",[103,704,705],{"class":616}," (",[103,707,708],{"class":545},"{\n",[103,710,712,715,718,721,723,726],{"class":105,"line":711},12,[103,713,714],{"class":616},"      geom",[103,716,717],{"class":545},":",[103,719,720],{"class":558}," feature",[103,722,610],{"class":545},[103,724,725],{"class":558},"geometry",[103,727,728],{"class":545},",\n",[103,730,732,735,737,739,741,744,747],{"class":105,"line":731},13,[103,733,734],{"class":616},"      properties",[103,736,717],{"class":545},[103,738,720],{"class":558},[103,740,610],{"class":545},[103,742,743],{"class":558},"properties",[103,745,746],{"class":545}," ||",[103,748,749],{"class":545}," {}\n",[103,751,753,756],{"class":105,"line":752},14,[103,754,755],{"class":545},"    }",[103,757,645],{"class":616},[103,759,761],{"class":105,"line":760},15,[103,762,763],{"emptyLinePlaceholder":54},"\n",[103,765,767,770,772,775,777,780,783,786,788,790,793,796,799,801],{"class":105,"line":766},16,[103,768,769],{"class":558},"    console",[103,771,610],{"class":545},[103,773,774],{"class":541},"log",[103,776,617],{"class":616},[103,778,779],{"class":545},"`",[103,781,782],{"class":638},"Importing ",[103,784,785],{"class":545},"${",[103,787,689],{"class":558},[103,789,610],{"class":545},[103,791,792],{"class":558},"length",[103,794,795],{"class":545},"}",[103,797,798],{"class":638}," features...",[103,800,779],{"class":545},[103,802,803],{"class":616},")\n",[103,805,807],{"class":105,"line":806},17,[103,808,651],{"class":616},[103,810,812,815,817,820,823,825,828,831,833,836,838,840,842,844,846,849,851,854],{"class":105,"line":811},18,[103,813,814],{"class":589},"    for",[103,816,705],{"class":616},[103,818,819],{"class":534},"let",[103,821,822],{"class":558}," i",[103,824,604],{"class":545},[103,826,827],{"class":671}," 0",[103,829,830],{"class":545},";",[103,832,822],{"class":558},[103,834,835],{"class":545}," \u003C",[103,837,680],{"class":558},[103,839,610],{"class":545},[103,841,792],{"class":558},[103,843,830],{"class":545},[103,845,822],{"class":558},[103,847,848],{"class":545}," +=",[103,850,666],{"class":558},[103,852,853],{"class":616},") ",[103,855,708],{"class":545},[103,857,859,862,865,867,869,871,874,876,879,881,883,886,888],{"class":105,"line":858},19,[103,860,861],{"class":534},"      const",[103,863,864],{"class":558}," batch",[103,866,604],{"class":545},[103,868,680],{"class":558},[103,870,610],{"class":545},[103,872,873],{"class":541},"slice",[103,875,617],{"class":616},[103,877,878],{"class":558},"i",[103,880,555],{"class":545},[103,882,822],{"class":558},[103,884,885],{"class":545}," +",[103,887,666],{"class":558},[103,889,803],{"class":616},[103,891,893,895,898,901,903,906,909,911,914],{"class":105,"line":892},20,[103,894,861],{"class":534},[103,896,897],{"class":545}," {",[103,899,900],{"class":558}," data",[103,902,555],{"class":545},[103,904,905],{"class":558}," error",[103,907,908],{"class":545}," }",[103,910,604],{"class":545},[103,912,913],{"class":589}," await",[103,915,916],{"class":558}," supabaseClient\n",[103,918,920,923,926,928,931],{"class":105,"line":919},21,[103,921,922],{"class":545},"        .",[103,924,925],{"class":541},"from",[103,927,617],{"class":616},[103,929,930],{"class":558},"tableName",[103,932,803],{"class":616},[103,934,936,938,941,943,946],{"class":105,"line":935},22,[103,937,922],{"class":545},[103,939,940],{"class":541},"insert",[103,942,617],{"class":616},[103,944,945],{"class":558},"batch",[103,947,803],{"class":616},[103,949,951],{"class":105,"line":950},23,[103,952,953],{"class":616},"      \n",[103,955,957,960,962,965,967],{"class":105,"line":956},24,[103,958,959],{"class":589},"      if",[103,961,705],{"class":616},[103,963,964],{"class":558},"error",[103,966,853],{"class":616},[103,968,708],{"class":545},[103,970,972,975,977,979,981,983,986,988,990,992],{"class":105,"line":971},25,[103,973,974],{"class":558},"        console",[103,976,610],{"class":545},[103,978,964],{"class":541},[103,980,617],{"class":616},[103,982,642],{"class":545},[103,984,985],{"class":638},"Error inserting batch:",[103,987,642],{"class":545},[103,989,555],{"class":545},[103,991,905],{"class":558},[103,993,803],{"class":616},[103,995,997],{"class":105,"line":996},26,[103,998,999],{"class":589},"        break\n",[103,1001,1003],{"class":105,"line":1002},27,[103,1004,1005],{"class":545},"      }\n",[103,1007,1009],{"class":105,"line":1008},28,[103,1010,953],{"class":616},[103,1012,1014,1017,1019,1021,1023,1025,1028,1030,1033,1035,1038,1041,1044,1047,1050,1053,1055,1057,1059,1061,1063,1066,1069,1071,1073,1075,1078,1081],{"class":105,"line":1013},29,[103,1015,1016],{"class":558},"      console",[103,1018,610],{"class":545},[103,1020,774],{"class":541},[103,1022,617],{"class":616},[103,1024,779],{"class":545},[103,1026,1027],{"class":638},"Inserted batch ",[103,1029,785],{"class":545},[103,1031,1032],{"class":558},"Math",[103,1034,610],{"class":545},[103,1036,1037],{"class":541},"floor",[103,1039,1040],{"class":558},"(i",[103,1042,1043],{"class":545},"\u002F",[103,1045,1046],{"class":558},"batchSize) ",[103,1048,1049],{"class":545},"+",[103,1051,1052],{"class":671}," 1",[103,1054,795],{"class":545},[103,1056,1043],{"class":638},[103,1058,785],{"class":545},[103,1060,1032],{"class":558},[103,1062,610],{"class":545},[103,1064,1065],{"class":541},"ceil",[103,1067,1068],{"class":558},"(features",[103,1070,610],{"class":545},[103,1072,792],{"class":558},[103,1074,1043],{"class":545},[103,1076,1077],{"class":558},"batchSize)",[103,1079,1080],{"class":545},"}`",[103,1082,803],{"class":616},[103,1084,1086],{"class":105,"line":1085},30,[103,1087,1088],{"class":545},"    }\n",[103,1090,1092],{"class":105,"line":1091},31,[103,1093,651],{"class":616},[103,1095,1097,1099,1101,1103,1105,1107,1110,1112],{"class":105,"line":1096},32,[103,1098,769],{"class":558},[103,1100,610],{"class":545},[103,1102,774],{"class":541},[103,1104,617],{"class":616},[103,1106,642],{"class":545},[103,1108,1109],{"class":638},"Import completed!",[103,1111,642],{"class":545},[103,1113,803],{"class":616},[103,1115,1117,1120,1123,1125,1127,1129],{"class":105,"line":1116},33,[103,1118,1119],{"class":545},"  }",[103,1121,1122],{"class":589}," catch",[103,1124,705],{"class":616},[103,1126,964],{"class":558},[103,1128,853],{"class":616},[103,1130,708],{"class":545},[103,1132,1134,1136,1138,1140,1142,1144,1147,1149,1151,1153],{"class":105,"line":1133},34,[103,1135,769],{"class":558},[103,1137,610],{"class":545},[103,1139,964],{"class":541},[103,1141,617],{"class":616},[103,1143,642],{"class":545},[103,1145,1146],{"class":638},"Import failed:",[103,1148,642],{"class":545},[103,1150,555],{"class":545},[103,1152,905],{"class":558},[103,1154,803],{"class":616},[103,1156,1158],{"class":105,"line":1157},35,[103,1159,1160],{"class":545},"  }\n",[103,1162,1164],{"class":105,"line":1163},36,[103,1165,1166],{"class":545},"}\n",[69,1168,1169],{},"This function can work perfectly as an Edge Function. However, for the sake of the \"minimum viable\" aspect of the project I just ran it as a local Node.JS application",[475,1171,1173],{"id":1172},"dynamic-vector-tile-querying","Dynamic Vector Tile Querying",[69,1175,1176,1177,1181,1182,1187,1188,1193],{},"Early on the client mentioned the desire to use Mapbox styling on the frontend while avoiding vendor lock-in risks on the data storage. This posed an interesting challenge to me, namely, ",[1178,1179,1180],"em",{},"'how can I render map layers natively on the frontend while keeping the data completely external to Mapbox?'",". Lucky for me, Mapbox allows for ",[295,1183,1186],{"href":1184,"rel":1185},"https:\u002F\u002Fdocs.mapbox.com\u002Fmapbox-gl-js\u002Fapi\u002Fsources\u002F#vectortilesource",[299],"querying external sources"," by sending simple HTTP requests to a given endpoint using the users current bounding box in the query parameters (example later on). The response has to conform to the Mapbox ",[295,1189,1192],{"href":1190,"rel":1191},"https:\u002F\u002Fmapbox.github.io\u002Fvector-tile-spec\u002F",[299],"Vector Tile specification"," (basically Google's Protocol Buffer format).",[69,1195,1196],{},"Using Supabase made things slighlty easier. Using their Edge Functions I could quickly setup a read-only endpoint to receive the incoming Mapbox requests. This Edge Function internally calls an RPC function which does the heavy lifting in terms of geometry fetching and processing. Below you can see a simplified outline main Edge Function body. Notice how I added a custom query parameter allowing for a dynamic dataset selection.",[520,1198,1199],{},[92,1200,1205],{"className":1201,"code":1202,"filename":1203,"language":1204,"meta":98,"style":98},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","serve(async (req)=>{\n  \u002F\u002F Parses query parameters from url \n  \u002F\u002F example: \u002Ffunctions\u002Fmvt\u002Fdataset123\u002F{z}\u002F{x}\u002F{y}\n  const url = new URL(req.url);\n  const parts = url.pathname.split('\u002F').filter((p)=>p.length > 0);\n  const mvtIndex = parts.indexOf('mvt');\n  const dataset = parts[mvtIndex + 1];\n  const z = Number(parts[mvtIndex + 2]);\n  const x = Number(parts[mvtIndex + 3]);\n  const y = Number(parts[mvtIndex + 4]);\n\n  \u002F\u002F Calls supabase RPC function to query the actual geo data\n  \u002F\u002F Args are parsed earlier from query parameters\n  const { data: vectorTileBinary, error } = await supabase.rpc('get_dynamic_lines_mvt', {\n    dataset,\n    z,\n    x,\n    y\n  });\n \n  \u002F\u002F Returns mvt data in raw bytes\n  return new Response(vectorTileBinary, {\n    status: 200,\n    headers: {\n      ...corsHeaders,\n      'Content-Type': 'application\u002Fvnd.mapbox-vector-tile'\n    }\n  });\n});\n","Supabase Edge Function","typescript",[100,1206,1207,1228,1233,1238,1268,1329,1358,1384,1415,1443,1471,1475,1480,1485,1529,1536,1543,1550,1555,1563,1567,1572,1591,1603,1612,1622,1642,1646,1654],{"__ignoreMap":98},[103,1208,1209,1212,1214,1216,1218,1221,1223,1226],{"class":105,"line":106},[103,1210,1211],{"class":541},"serve",[103,1213,617],{"class":558},[103,1215,535],{"class":534},[103,1217,705],{"class":545},[103,1219,1220],{"class":551},"req",[103,1222,580],{"class":545},[103,1224,1225],{"class":534},"=>",[103,1227,708],{"class":545},[103,1229,1230],{"class":105,"line":112},[103,1231,1232],{"class":657},"  \u002F\u002F Parses query parameters from url \n",[103,1234,1235],{"class":105,"line":118},[103,1236,1237],{"class":657},"  \u002F\u002F example: \u002Ffunctions\u002Fmvt\u002Fdataset123\u002F{z}\u002F{x}\u002F{y}\n",[103,1239,1240,1243,1246,1248,1251,1254,1256,1258,1260,1263,1265],{"class":105,"line":571},[103,1241,1242],{"class":534},"  const",[103,1244,1245],{"class":558}," url",[103,1247,604],{"class":545},[103,1249,1250],{"class":545}," new",[103,1252,1253],{"class":541}," URL",[103,1255,617],{"class":616},[103,1257,1220],{"class":558},[103,1259,610],{"class":545},[103,1261,1262],{"class":558},"url",[103,1264,580],{"class":616},[103,1266,1267],{"class":545},";\n",[103,1269,1270,1272,1275,1277,1279,1281,1284,1286,1289,1291,1293,1295,1297,1299,1301,1304,1306,1308,1310,1312,1314,1316,1318,1320,1323,1325,1327],{"class":105,"line":577},[103,1271,1242],{"class":534},[103,1273,1274],{"class":558}," parts",[103,1276,604],{"class":545},[103,1278,1245],{"class":558},[103,1280,610],{"class":545},[103,1282,1283],{"class":558},"pathname",[103,1285,610],{"class":545},[103,1287,1288],{"class":541},"split",[103,1290,617],{"class":616},[103,1292,642],{"class":545},[103,1294,1043],{"class":638},[103,1296,642],{"class":545},[103,1298,580],{"class":616},[103,1300,610],{"class":545},[103,1302,1303],{"class":541},"filter",[103,1305,617],{"class":616},[103,1307,617],{"class":545},[103,1309,69],{"class":551},[103,1311,580],{"class":545},[103,1313,1225],{"class":534},[103,1315,69],{"class":558},[103,1317,610],{"class":545},[103,1319,792],{"class":558},[103,1321,1322],{"class":545}," >",[103,1324,827],{"class":671},[103,1326,580],{"class":616},[103,1328,1267],{"class":545},[103,1330,1331,1333,1336,1338,1340,1342,1345,1347,1349,1352,1354,1356],{"class":105,"line":586},[103,1332,1242],{"class":534},[103,1334,1335],{"class":558}," mvtIndex",[103,1337,604],{"class":545},[103,1339,1274],{"class":558},[103,1341,610],{"class":545},[103,1343,1344],{"class":541},"indexOf",[103,1346,617],{"class":616},[103,1348,642],{"class":545},[103,1350,1351],{"class":638},"mvt",[103,1353,642],{"class":545},[103,1355,580],{"class":616},[103,1357,1267],{"class":545},[103,1359,1360,1362,1365,1367,1369,1372,1375,1377,1379,1382],{"class":105,"line":595},[103,1361,1242],{"class":534},[103,1363,1364],{"class":558}," dataset",[103,1366,604],{"class":545},[103,1368,1274],{"class":558},[103,1370,1371],{"class":616},"[",[103,1373,1374],{"class":558},"mvtIndex",[103,1376,885],{"class":545},[103,1378,1052],{"class":671},[103,1380,1381],{"class":616},"]",[103,1383,1267],{"class":545},[103,1385,1386,1388,1391,1393,1396,1398,1401,1403,1405,1407,1410,1413],{"class":105,"line":648},[103,1387,1242],{"class":534},[103,1389,1390],{"class":558}," z",[103,1392,604],{"class":545},[103,1394,1395],{"class":541}," Number",[103,1397,617],{"class":616},[103,1399,1400],{"class":558},"parts",[103,1402,1371],{"class":616},[103,1404,1374],{"class":558},[103,1406,885],{"class":545},[103,1408,1409],{"class":671}," 2",[103,1411,1412],{"class":616},"])",[103,1414,1267],{"class":545},[103,1416,1417,1419,1422,1424,1426,1428,1430,1432,1434,1436,1439,1441],{"class":105,"line":654},[103,1418,1242],{"class":534},[103,1420,1421],{"class":558}," x",[103,1423,604],{"class":545},[103,1425,1395],{"class":541},[103,1427,617],{"class":616},[103,1429,1400],{"class":558},[103,1431,1371],{"class":616},[103,1433,1374],{"class":558},[103,1435,885],{"class":545},[103,1437,1438],{"class":671}," 3",[103,1440,1412],{"class":616},[103,1442,1267],{"class":545},[103,1444,1445,1447,1450,1452,1454,1456,1458,1460,1462,1464,1467,1469],{"class":105,"line":661},[103,1446,1242],{"class":534},[103,1448,1449],{"class":558}," y",[103,1451,604],{"class":545},[103,1453,1395],{"class":541},[103,1455,617],{"class":616},[103,1457,1400],{"class":558},[103,1459,1371],{"class":616},[103,1461,1374],{"class":558},[103,1463,885],{"class":545},[103,1465,1466],{"class":671}," 4",[103,1468,1412],{"class":616},[103,1470,1267],{"class":545},[103,1472,1473],{"class":105,"line":675},[103,1474,763],{"emptyLinePlaceholder":54},[103,1476,1477],{"class":105,"line":711},[103,1478,1479],{"class":657},"  \u002F\u002F Calls supabase RPC function to query the actual geo data\n",[103,1481,1482],{"class":105,"line":731},[103,1483,1484],{"class":657},"  \u002F\u002F Args are parsed earlier from query parameters\n",[103,1486,1487,1489,1491,1493,1495,1498,1500,1502,1504,1506,1508,1511,1513,1516,1518,1520,1523,1525,1527],{"class":105,"line":752},[103,1488,1242],{"class":534},[103,1490,897],{"class":545},[103,1492,900],{"class":616},[103,1494,717],{"class":545},[103,1496,1497],{"class":558}," vectorTileBinary",[103,1499,555],{"class":545},[103,1501,905],{"class":558},[103,1503,908],{"class":545},[103,1505,604],{"class":545},[103,1507,913],{"class":589},[103,1509,1510],{"class":558}," supabase",[103,1512,610],{"class":545},[103,1514,1515],{"class":541},"rpc",[103,1517,617],{"class":616},[103,1519,642],{"class":545},[103,1521,1522],{"class":638},"get_dynamic_lines_mvt",[103,1524,642],{"class":545},[103,1526,555],{"class":545},[103,1528,583],{"class":545},[103,1530,1531,1534],{"class":105,"line":760},[103,1532,1533],{"class":558},"    dataset",[103,1535,728],{"class":545},[103,1537,1538,1541],{"class":105,"line":766},[103,1539,1540],{"class":558},"    z",[103,1542,728],{"class":545},[103,1544,1545,1548],{"class":105,"line":806},[103,1546,1547],{"class":558},"    x",[103,1549,728],{"class":545},[103,1551,1552],{"class":105,"line":811},[103,1553,1554],{"class":558},"    y\n",[103,1556,1557,1559,1561],{"class":105,"line":858},[103,1558,1119],{"class":545},[103,1560,580],{"class":616},[103,1562,1267],{"class":545},[103,1564,1565],{"class":105,"line":892},[103,1566,559],{"class":616},[103,1568,1569],{"class":105,"line":919},[103,1570,1571],{"class":657},"  \u002F\u002F Returns mvt data in raw bytes\n",[103,1573,1574,1577,1579,1582,1584,1587,1589],{"class":105,"line":935},[103,1575,1576],{"class":589},"  return",[103,1578,1250],{"class":545},[103,1580,1581],{"class":541}," Response",[103,1583,617],{"class":616},[103,1585,1586],{"class":558},"vectorTileBinary",[103,1588,555],{"class":545},[103,1590,583],{"class":545},[103,1592,1593,1596,1598,1601],{"class":105,"line":950},[103,1594,1595],{"class":616},"    status",[103,1597,717],{"class":545},[103,1599,1600],{"class":671}," 200",[103,1602,728],{"class":545},[103,1604,1605,1608,1610],{"class":105,"line":956},[103,1606,1607],{"class":616},"    headers",[103,1609,717],{"class":545},[103,1611,583],{"class":545},[103,1613,1614,1617,1620],{"class":105,"line":971},[103,1615,1616],{"class":545},"      ...",[103,1618,1619],{"class":558},"corsHeaders",[103,1621,728],{"class":545},[103,1623,1624,1627,1630,1632,1634,1636,1639],{"class":105,"line":996},[103,1625,1626],{"class":545},"      '",[103,1628,1629],{"class":616},"Content-Type",[103,1631,642],{"class":545},[103,1633,717],{"class":545},[103,1635,635],{"class":545},[103,1637,1638],{"class":638},"application\u002Fvnd.mapbox-vector-tile",[103,1640,1641],{"class":545},"'\n",[103,1643,1644],{"class":105,"line":1002},[103,1645,1088],{"class":545},[103,1647,1648,1650,1652],{"class":105,"line":1008},[103,1649,1119],{"class":545},[103,1651,580],{"class":616},[103,1653,1267],{"class":545},[103,1655,1656,1658,1660],{"class":105,"line":1013},[103,1657,795],{"class":545},[103,1659,580],{"class":558},[103,1661,1267],{"class":545},[69,1663,1664,1665,1667],{},"Now for the interesting part. The PostgreSQL ",[100,1666,1522],{}," RPC function was made to have very rudamentary API, as shown below:",[92,1669,1674],{"className":1670,"code":1671,"filename":1672,"language":1673,"meta":98,"style":98},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","CREATE OR REPLACE FUNCTION public.get_dynamic_lines_mvt(\n  dataset text,\n  z integer,\n  x integer,\n  y integer\n) RETURNS bytea\n","PostgreSQL","ts",[100,1675,1676,1687,1694,1701,1708,1713],{"__ignoreMap":98},[103,1677,1678,1681,1683,1685],{"class":105,"line":106},[103,1679,1680],{"class":558},"CREATE OR REPLACE FUNCTION public",[103,1682,610],{"class":545},[103,1684,1522],{"class":541},[103,1686,546],{"class":558},[103,1688,1689,1692],{"class":105,"line":112},[103,1690,1691],{"class":558},"  dataset text",[103,1693,728],{"class":545},[103,1695,1696,1699],{"class":105,"line":118},[103,1697,1698],{"class":558},"  z integer",[103,1700,728],{"class":545},[103,1702,1703,1706],{"class":105,"line":571},[103,1704,1705],{"class":558},"  x integer",[103,1707,728],{"class":545},[103,1709,1710],{"class":105,"line":577},[103,1711,1712],{"class":558},"  y integer\n",[103,1714,1715],{"class":105,"line":586},[103,1716,1717],{"class":558},") RETURNS bytea\n",[69,1719,1720],{},"What actually happens inside of the function body can best be described in a handful of procedural steps.",[69,1722,1723],{},"Firstly, the sent coordinates values (x, y, z) are translated into rectangular polygon coordinates representing the user's bounding box.",[92,1725,1727],{"className":1670,"code":1726,"filename":1672,"language":1673,"meta":98,"style":98},"bounds3857 := ST_TileEnvelope(z, x, y);\n",[100,1728,1729],{"__ignoreMap":98},[103,1730,1731,1735,1738,1741,1744,1746,1748,1750,1753],{"class":105,"line":106},[103,1732,1734],{"class":1733},"sBMFI","bounds3857",[103,1736,1737],{"class":545}," :=",[103,1739,1740],{"class":541}," ST_TileEnvelope",[103,1742,1743],{"class":558},"(z",[103,1745,555],{"class":545},[103,1747,1421],{"class":558},[103,1749,555],{"class":545},[103,1751,1752],{"class":558}," y)",[103,1754,1267],{"class":545},[1756,1757,1758],"note",{},[69,1759,1760,1763,1764],{},[100,1761,1762],{},"bounds385"," is generated and later injected at runtime using alias ",[100,1765,1766],{},"$1",[69,1768,1769],{},"Next, geometries that land outside of the requested tile are filtered out before moving on to expensive intersection computations.",[92,1771,1773],{"className":1670,"code":1772,"filename":1672,"language":1673,"meta":98,"style":98},"WITH filtered AS (\n  SELECT ST_Transform(wkb_geometry, 3857) AS geom3857 FROM %I.%I\n  WHERE ST_Transform(wkb_geometry, 3857) && $1::geometry\n  AND ST_Intersects(ST_Transform(wkb_geometry, 3857), $1::geometry)\n),\n",[100,1774,1775,1786,1817,1838,1863],{"__ignoreMap":98},[103,1776,1777,1780,1783],{"class":105,"line":106},[103,1778,1779],{"class":558},"WITH filtered ",[103,1781,1782],{"class":541},"AS",[103,1784,1785],{"class":558}," (\n",[103,1787,1788,1791,1794,1797,1799,1802,1805,1808,1811,1814],{"class":105,"line":112},[103,1789,1790],{"class":558},"  SELECT ",[103,1792,1793],{"class":541},"ST_Transform",[103,1795,1796],{"class":558},"(wkb_geometry",[103,1798,555],{"class":545},[103,1800,1801],{"class":671}," 3857",[103,1803,1804],{"class":558},") AS geom3857 FROM ",[103,1806,1807],{"class":545},"%",[103,1809,1810],{"class":558},"I",[103,1812,1813],{"class":545},".%",[103,1815,1816],{"class":558},"I\n",[103,1818,1819,1822,1824,1826,1828,1830,1832,1835],{"class":105,"line":118},[103,1820,1821],{"class":558},"  WHERE ",[103,1823,1793],{"class":541},[103,1825,1796],{"class":558},[103,1827,555],{"class":545},[103,1829,1801],{"class":671},[103,1831,853],{"class":558},[103,1833,1834],{"class":545},"&&",[103,1836,1837],{"class":558}," $1::geometry\n",[103,1839,1840,1843,1846,1848,1850,1852,1854,1856,1858,1860],{"class":105,"line":571},[103,1841,1842],{"class":558},"  AND ",[103,1844,1845],{"class":541},"ST_Intersects",[103,1847,617],{"class":558},[103,1849,1793],{"class":541},[103,1851,1796],{"class":558},[103,1853,555],{"class":545},[103,1855,1801],{"class":671},[103,1857,580],{"class":558},[103,1859,555],{"class":545},[103,1861,1862],{"class":558}," $1::geometry)\n",[103,1864,1865,1867],{"class":105,"line":577},[103,1866,580],{"class":558},[103,1868,728],{"class":545},[69,1870,1871],{},"To further help performance, before any of the expensive geometry checks are done, the caller's z-value gets evaluated and classified. This is done in oreder to not overfetch very detailed geometry data for a large zoomed-out bounding box when geometry resolution is least important.",[92,1873,1875],{"className":1670,"code":1874,"filename":1672,"language":1673,"meta":98,"style":98},"simplification := CASE\n  WHEN z \u003C= 5  THEN 80\n  WHEN z \u003C= 10 THEN 15\n  ELSE 0\nEND;\n",[100,1876,1877,1887,1904,1919,1927],{"__ignoreMap":98},[103,1878,1879,1882,1884],{"class":105,"line":106},[103,1880,1881],{"class":1733},"simplification",[103,1883,1737],{"class":545},[103,1885,1886],{"class":558}," CASE\n",[103,1888,1889,1892,1895,1898,1901],{"class":105,"line":112},[103,1890,1891],{"class":558},"  WHEN z ",[103,1893,1894],{"class":545},"\u003C=",[103,1896,1897],{"class":671}," 5",[103,1899,1900],{"class":558},"  THEN ",[103,1902,1903],{"class":671},"80\n",[103,1905,1906,1908,1910,1913,1916],{"class":105,"line":118},[103,1907,1891],{"class":558},[103,1909,1894],{"class":545},[103,1911,1912],{"class":671}," 10",[103,1914,1915],{"class":558}," THEN ",[103,1917,1918],{"class":671},"15\n",[103,1920,1921,1924],{"class":105,"line":571},[103,1922,1923],{"class":558},"  ELSE ",[103,1925,1926],{"class":671},"0\n",[103,1928,1929,1932],{"class":105,"line":577},[103,1930,1931],{"class":558},"END",[103,1933,1267],{"class":545},[1756,1935,1936],{},[69,1937,1938,1939,1941,1942,1945,1946],{},"Just as ",[100,1940,1762],{},", ",[100,1943,1944],{},"simplifications"," is also generated and injected at runtime using alias ",[100,1947,1948],{},"$2",[69,1950,1951,1952,1955,1956,610],{},"This value is subsequently used as an argument to clip and simplify the data to varying degrees. At the same time, ",[100,1953,1954],{},"ST_AsMVTGeom"," turns the simplified geometries from \"Well-Known Binary\" representation found in the actual table into the coordinate space of a MVT tile stored in ",[100,1957,1958],{},"mvtgeom",[92,1960,1962],{"className":1670,"code":1961,"filename":1672,"language":1673,"meta":98,"style":98},"mvtgeom AS (\n  SELECT\n    ST_AsMVTGeom(\n      CASE\n        WHEN $2 > 0\n          THEN ST_SimplifyPreserveTopology(geom3857, $2)\n        ELSE geom3857\n      END,\n      $1, 4096, 0, true\n    ) AS geom\n  FROM filtered\n)\n",[100,1963,1964,1973,1978,1985,1990,2001,2017,2022,2029,2049,2054,2059],{"__ignoreMap":98},[103,1965,1966,1969,1971],{"class":105,"line":106},[103,1967,1968],{"class":558},"mvtgeom ",[103,1970,1782],{"class":541},[103,1972,1785],{"class":558},[103,1974,1975],{"class":105,"line":112},[103,1976,1977],{"class":558},"  SELECT\n",[103,1979,1980,1983],{"class":105,"line":118},[103,1981,1982],{"class":541},"    ST_AsMVTGeom",[103,1984,546],{"class":558},[103,1986,1987],{"class":105,"line":571},[103,1988,1989],{"class":558},"      CASE\n",[103,1991,1992,1995,1998],{"class":105,"line":577},[103,1993,1994],{"class":558},"        WHEN $2 ",[103,1996,1997],{"class":545},">",[103,1999,2000],{"class":671}," 0\n",[103,2002,2003,2006,2009,2012,2014],{"class":105,"line":586},[103,2004,2005],{"class":558},"          THEN ",[103,2007,2008],{"class":541},"ST_SimplifyPreserveTopology",[103,2010,2011],{"class":558},"(geom3857",[103,2013,555],{"class":545},[103,2015,2016],{"class":558}," $2)\n",[103,2018,2019],{"class":105,"line":595},[103,2020,2021],{"class":558},"        ELSE geom3857\n",[103,2023,2024,2027],{"class":105,"line":648},[103,2025,2026],{"class":558},"      END",[103,2028,728],{"class":545},[103,2030,2031,2034,2036,2039,2041,2043,2045],{"class":105,"line":654},[103,2032,2033],{"class":558},"      $1",[103,2035,555],{"class":545},[103,2037,2038],{"class":671}," 4096",[103,2040,555],{"class":545},[103,2042,827],{"class":671},[103,2044,555],{"class":545},[103,2046,2048],{"class":2047},"sfNiH"," true\n",[103,2050,2051],{"class":105,"line":661},[103,2052,2053],{"class":558},"    ) AS geom\n",[103,2055,2056],{"class":105,"line":675},[103,2057,2058],{"class":558},"  FROM filtered\n",[103,2060,2061],{"class":105,"line":711},[103,2062,803],{"class":558},[69,2064,2065],{},"Finally, filtered and simplified data is serialized into raw mvt bytes to be returned to the caller and later the user.",[92,2067,2069],{"className":1670,"code":2068,"filename":1672,"language":1673,"meta":98,"style":98},"SELECT ST_AsMVT(\n  q,\n  'lines',\n  4096,\n  'geom'\n)\nFROM (\n  SELECT geom\n  FROM mvtgeom\n) q;\n",[100,2070,2071,2081,2088,2100,2107,2116,2120,2127,2132,2137],{"__ignoreMap":98},[103,2072,2073,2076,2079],{"class":105,"line":106},[103,2074,2075],{"class":558},"SELECT ",[103,2077,2078],{"class":541},"ST_AsMVT",[103,2080,546],{"class":558},[103,2082,2083,2086],{"class":105,"line":112},[103,2084,2085],{"class":558},"  q",[103,2087,728],{"class":545},[103,2089,2090,2093,2096,2098],{"class":105,"line":118},[103,2091,2092],{"class":545},"  '",[103,2094,2095],{"class":638},"lines",[103,2097,642],{"class":545},[103,2099,728],{"class":545},[103,2101,2102,2105],{"class":105,"line":571},[103,2103,2104],{"class":671},"  4096",[103,2106,728],{"class":545},[103,2108,2109,2111,2114],{"class":105,"line":577},[103,2110,2092],{"class":545},[103,2112,2113],{"class":638},"geom",[103,2115,1641],{"class":545},[103,2117,2118],{"class":105,"line":586},[103,2119,803],{"class":558},[103,2121,2122,2125],{"class":105,"line":595},[103,2123,2124],{"class":541},"FROM",[103,2126,1785],{"class":558},[103,2128,2129],{"class":105,"line":648},[103,2130,2131],{"class":558},"  SELECT geom\n",[103,2133,2134],{"class":105,"line":654},[103,2135,2136],{"class":558},"  FROM mvtgeom\n",[103,2138,2139,2142],{"class":105,"line":661},[103,2140,2141],{"class":558},") q",[103,2143,1267],{"class":545},[69,2145,2146,2147,2152],{},"As you may already have noticed, in order to achieve much of the geometry translation and serialization functionallity I made use of PostGIS' exhaustive list of Special Functions. Perhaps this post is not the ideal place to go into detail on them so I highly recommend checking out the official ",[295,2148,2151],{"href":2149,"rel":2150},"https:\u002F\u002Fpostgis.net\u002Fdocs\u002FPostGIS_Special_Functions_Index.html",[299],"PostGIS Special Function Index"," in case you'd like to know more. The main Special Functions I ended up using were the following:",[434,2154,2155],{},[92,2156,2158],{"className":1670,"code":2157,"language":1673,"meta":98,"style":98},"ST_TileEnvelope()\nST_Transform()\nST_SimplifyPreserveTopology()\nST_Intersects()\nST_AsMVTGeom()\nST_AsMVT()\n",[100,2159,2160,2168,2174,2180,2186,2192],{"__ignoreMap":98},[103,2161,2162,2165],{"class":105,"line":106},[103,2163,2164],{"class":541},"ST_TileEnvelope",[103,2166,2167],{"class":558},"()\n",[103,2169,2170,2172],{"class":105,"line":112},[103,2171,1793],{"class":541},[103,2173,2167],{"class":558},[103,2175,2176,2178],{"class":105,"line":118},[103,2177,2008],{"class":541},[103,2179,2167],{"class":558},[103,2181,2182,2184],{"class":105,"line":571},[103,2183,1845],{"class":541},[103,2185,2167],{"class":558},[103,2187,2188,2190],{"class":105,"line":577},[103,2189,1954],{"class":541},[103,2191,2167],{"class":558},[103,2193,2194,2196],{"class":105,"line":586},[103,2195,2078],{"class":541},[103,2197,2167],{"class":558},[168,2199,2200],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":98,"searchDepth":112,"depth":112,"links":2202},[2203,2204,2205,2206,2207],{"id":222,"depth":118,"text":223},{"id":431,"depth":112,"text":432},{"id":444,"depth":112,"text":445},{"id":451,"depth":112,"text":452},{"id":472,"depth":112,"text":473},"#137E6E","2025-10-01","Client project where I was tasked with building a full-stack energy grid visualization platform by ingesting open municipality data into a PostGIS databse.","content\u002Ffocal-grid-banner.png",{},"Full-Stack GIS App",{"title":36,"description":2210},[2216,2217,2218,2219],"TypeScript","Nuxt.js","Mapbox SDK","PostGIS","ZKB6mbMU6LBl33krBX0ge03owrD65U-ujLG04jc7zuo",{"id":59,"title":28,"body":2222,"color":175,"date":176,"description":177,"extension":178,"image":179,"meta":2286,"navigation":54,"oneliner":181,"path":29,"seo":2287,"stem":30,"tags":2288,"url":188,"__hash__":189},{"type":61,"value":2223,"toc":2282},[2224,2226,2228,2230,2234,2236,2252,2254,2276,2280],[64,2225,67],{"id":66},[69,2227,71],{},[64,2229,75],{"id":74},[77,2231,2232],{"icon":79,"target":80,"title":81,"to":82},[69,2233,85],{},[87,2235,90],{"id":89},[92,2237,2238],{"className":94,"code":95,"filename":96,"language":97,"meta":98,"style":98},[100,2239,2240,2244,2248],{"__ignoreMap":98},[103,2241,2242],{"class":105,"line":106},[103,2243,109],{},[103,2245,2246],{"class":105,"line":112},[103,2247,115],{},[103,2249,2250],{"class":105,"line":118},[103,2251,121],{},[87,2253,125],{"id":124},[127,2255,2256,2264],{},[130,2257,2258],{},[133,2259,2260,2262],{},[136,2261,138],{},[136,2263,141],{},[143,2265,2266],{},[133,2267,2268,2272],{},[148,2269,2270],{},[151,2271],{"alt":153,"src":154},[148,2273,2274],{},[151,2275],{"alt":159,"src":160},[69,2277,2278],{},[151,2279],{"alt":165,"src":166},[168,2281,170],{},{"title":98,"searchDepth":112,"depth":112,"links":2283},[2284,2285],{"id":66,"depth":112,"text":67},{"id":74,"depth":112,"text":75},{},{"title":28,"description":177},[184,185,186,187],[50,2290],{"title":32,"path":33,"stem":34,"description":206,"children":-1},1786205226165]