6944 lines
234 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Build Power. Not Rent It. Own your digital infrastructure.">
<meta name="author" content="Bunker Operations">
<link rel="canonical" href="https://bnkserve.org/v2/features/map/locations/">
<link rel="prev" href="../">
<link rel="next" href="../geocoding/">
<link rel="icon" href="../../../../assets/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
<title>Locations - Changemaker Lite</title>
<link rel="stylesheet" href="../../../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="../../../../stylesheets/extra.css">
<link rel="stylesheet" href="../../../../stylesheets/home.css">
<link rel="stylesheet" href="../../../../assets/css/video-player.css">
<script>__md_scope=new URL("../../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
<meta property="og:type" content="website" />
<meta property="og:title" content="Locations - Changemaker Lite" />
<meta property="og:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="og:image" content="https://bnkserve.org/assets/images/social/v2/features/map/locations.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://bnkserve.org/v2/features/map/locations/" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="Locations - Changemaker Lite" />
<meta property="twitter:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="twitter:image" content="https://bnkserve.org/assets/images/social/v2/features/map/locations.png" />
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#location-management-system" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../../../.." title="Changemaker Lite" class="md-header__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../../assets/logo.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Changemaker Lite
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Locations
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
</nav>
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../../../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../../../" class="md-tabs__link">
V2 Documentation
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../phil/" class="md-tabs__link">
Philosophy
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../v1/" class="md-tabs__link">
V1 Documentation (Legacy)
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../blog/" class="md-tabs__link">
Blog
</a>
</li>
</ul>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../../.." title="Changemaker Lite" class="md-nav__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../../assets/logo.png" alt="logo">
</a>
Changemaker Lite
</label>
<div class="md-nav__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
<div class="md-nav__link md-nav__container">
<a href="../../../" class="md-nav__link ">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
<label class="md-nav__link " for="__nav_2" id="__nav_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
V2 Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_2" >
<div class="md-nav__link md-nav__container">
<a href="../../../getting-started/" class="md-nav__link ">
<span class="md-ellipsis">
Getting Started
</span>
</a>
<label class="md-nav__link " for="__nav_2_2" id="__nav_2_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_2">
<span class="md-nav__icon md-icon"></span>
Getting Started
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../getting-started/quick-start/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_3" >
<div class="md-nav__link md-nav__container">
<a href="../../../architecture/" class="md-nav__link ">
<span class="md-ellipsis">
Architecture
</span>
</a>
<label class="md-nav__link " for="__nav_2_3" id="__nav_2_3_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_3">
<span class="md-nav__icon md-icon"></span>
Architecture
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../architecture/dual-api/" class="md-nav__link">
<span class="md-ellipsis">
Dual API System
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../architecture/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication & Security
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_4" >
<div class="md-nav__link md-nav__container">
<a href="../../../backend/" class="md-nav__link ">
<span class="md-ellipsis">
Backend
</span>
</a>
<label class="md-nav__link " for="__nav_2_4" id="__nav_2_4_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_4">
<span class="md-nav__icon md-icon"></span>
Backend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/modules/" class="md-nav__link">
<span class="md-ellipsis">
Modules
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/services/" class="md-nav__link">
<span class="md-ellipsis">
Services
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/middleware/" class="md-nav__link">
<span class="md-ellipsis">
Middleware
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/utilities/" class="md-nav__link">
<span class="md-ellipsis">
Utilities
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_5" >
<div class="md-nav__link md-nav__container">
<a href="../../../frontend/" class="md-nav__link ">
<span class="md-ellipsis">
Frontend
</span>
</a>
<label class="md-nav__link " for="__nav_2_5" id="__nav_2_5_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_5">
<span class="md-nav__icon md-icon"></span>
Frontend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/components/" class="md-nav__link">
<span class="md-ellipsis">
Components
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/layouts/" class="md-nav__link">
<span class="md-ellipsis">
Layouts
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/pages/" class="md-nav__link">
<span class="md-ellipsis">
Pages
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_6" >
<div class="md-nav__link md-nav__container">
<a href="../../../database/" class="md-nav__link ">
<span class="md-ellipsis">
Database
</span>
</a>
<label class="md-nav__link " for="__nav_2_6" id="__nav_2_6_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_6_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_6">
<span class="md-nav__icon md-icon"></span>
Database
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../database/schema/" class="md-nav__link">
<span class="md-ellipsis">
Schema Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/seeding/" class="md-nav__link">
<span class="md-ellipsis">
Seeding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/indexes/" class="md-nav__link">
<span class="md-ellipsis">
Indexes
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../database/models/" class="md-nav__link">
<span class="md-ellipsis">
Models
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_7" checked>
<div class="md-nav__link md-nav__container">
<a href="../../" class="md-nav__link ">
<span class="md-ellipsis">
Features
</span>
</a>
<label class="md-nav__link " for="__nav_2_7" id="__nav_2_7_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_7_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_7">
<span class="md-nav__icon md-icon"></span>
Features
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../influence/" class="md-nav__link">
<span class="md-ellipsis">
Influence
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_7_3" checked>
<div class="md-nav__link md-nav__container">
<a href="../" class="md-nav__link ">
<span class="md-ellipsis">
Map
</span>
</a>
<label class="md-nav__link " for="__nav_2_7_3" id="__nav_2_7_3_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_2_7_3_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_7_3">
<span class="md-nav__icon md-icon"></span>
Map
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Locations
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Locations
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#architecture" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-models" class="md-nav__link">
<span class="md-ellipsis">
Database Models
</span>
</a>
<nav class="md-nav" aria-label="Database Models">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#location-model" class="md-nav__link">
<span class="md-ellipsis">
Location Model
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#address-model" class="md-nav__link">
<span class="md-ellipsis">
Address Model
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-endpoints" class="md-nav__link">
<span class="md-ellipsis">
API Endpoints
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
<nav class="md-nav" aria-label="Configuration">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#environment-variables" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-indexes" class="md-nav__link">
<span class="md-ellipsis">
Database Indexes
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#admin-workflow" class="md-nav__link">
<span class="md-ellipsis">
Admin Workflow
</span>
</a>
<nav class="md-nav" aria-label="Admin Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-location" class="md-nav__link">
<span class="md-ellipsis">
Creating a Location
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#csv-import-workflow" class="md-nav__link">
<span class="md-ellipsis">
CSV Import Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-server-import-workflow" class="md-nav__link">
<span class="md-ellipsis">
NAR Server Import Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#bulk-re-geocoding" class="md-nav__link">
<span class="md-ellipsis">
Bulk Re-Geocoding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#exporting-locations" class="md-nav__link">
<span class="md-ellipsis">
Exporting Locations
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#public-workflow" class="md-nav__link">
<span class="md-ellipsis">
Public Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#volunteer-workflow" class="md-nav__link">
<span class="md-ellipsis">
Volunteer Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-examples" class="md-nav__link">
<span class="md-ellipsis">
Code Examples
</span>
</a>
<nav class="md-nav" aria-label="Code Examples">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-location-frontend" class="md-nav__link">
<span class="md-ellipsis">
Creating a Location (Frontend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#geocoding-an-address-frontend" class="md-nav__link">
<span class="md-ellipsis">
Geocoding an Address (Frontend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#location-service-create-backend" class="md-nav__link">
<span class="md-ellipsis">
Location Service Create (Backend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#csv-import-detection-backend" class="md-nav__link">
<span class="md-ellipsis">
CSV Import Detection (Backend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-lambert-coordinate-conversion-backend" class="md-nav__link">
<span class="md-ellipsis">
NAR Lambert Coordinate Conversion (Backend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#spatial-filtering-by-cut-backend" class="md-nav__link">
<span class="md-ellipsis">
Spatial Filtering by Cut (Backend)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<nav class="md-nav" aria-label="Troubleshooting">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#issue-geocoding-fails-for-valid-address" class="md-nav__link">
<span class="md-ellipsis">
Issue: Geocoding Fails for Valid Address
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#issue-nar-import-fails-or-hangs" class="md-nav__link">
<span class="md-ellipsis">
Issue: NAR Import Fails or Hangs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#issue-duplicate-locations-created-on-import" class="md-nav__link">
<span class="md-ellipsis">
Issue: Duplicate Locations Created on Import
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#issue-low-geocode-confidence-for-nar-data" class="md-nav__link">
<span class="md-ellipsis">
Issue: Low Geocode Confidence for NAR Data
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#performance-considerations" class="md-nav__link">
<span class="md-ellipsis">
Performance Considerations
</span>
</a>
<nav class="md-nav" aria-label="Performance Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#query-optimization" class="md-nav__link">
<span class="md-ellipsis">
Query Optimization
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#geocoding-rate-limits" class="md-nav__link">
<span class="md-ellipsis">
Geocoding Rate Limits
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-import-performance" class="md-nav__link">
<span class="md-ellipsis">
NAR Import Performance
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#map-rendering-performance" class="md-nav__link">
<span class="md-ellipsis">
Map Rendering Performance
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#related-documentation" class="md-nav__link">
<span class="md-ellipsis">
Related Documentation
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../geocoding/" class="md-nav__link">
<span class="md-ellipsis">
Geocoding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../cuts/" class="md-nav__link">
<span class="md-ellipsis">
Cuts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../shifts/" class="md-nav__link">
<span class="md-ellipsis">
Shifts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../canvassing/" class="md-nav__link">
<span class="md-ellipsis">
Canvassing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../tracking/" class="md-nav__link">
<span class="md-ellipsis">
Tracking
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../walk-sheets/" class="md-nav__link">
<span class="md-ellipsis">
Walk Sheets
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../nar-import/" class="md-nav__link">
<span class="md-ellipsis">
NAR Import
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../data-quality/" class="md-nav__link">
<span class="md-ellipsis">
Data Quality
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../landing-pages/" class="md-nav__link">
<span class="md-ellipsis">
Landing Pages
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../email-templates/" class="md-nav__link">
<span class="md-ellipsis">
Email Templates
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../media/" class="md-nav__link">
<span class="md-ellipsis">
Media
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../newsletter/" class="md-nav__link">
<span class="md-ellipsis">
Newsletter
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../observability/" class="md-nav__link">
<span class="md-ellipsis">
Observability
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../tunnel/" class="md-nav__link">
<span class="md-ellipsis">
Tunnel
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_8" >
<div class="md-nav__link md-nav__container">
<a href="../../../deployment/" class="md-nav__link ">
<span class="md-ellipsis">
Deployment
</span>
</a>
<label class="md-nav__link " for="__nav_2_8" id="__nav_2_8_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_8_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_8">
<span class="md-nav__icon md-icon"></span>
Deployment
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../deployment/docker-compose/" class="md-nav__link">
<span class="md-ellipsis">
Docker Compose
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/environment-variables/" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/nginx/" class="md-nav__link">
<span class="md-ellipsis">
Nginx Configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/ssl-tls/" class="md-nav__link">
<span class="md-ellipsis">
SSL/TLS
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/tunneling/" class="md-nav__link">
<span class="md-ellipsis">
Tunneling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/monitoring-stack/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Stack
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/healthchecks/" class="md-nav__link">
<span class="md-ellipsis">
Health Checks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/scaling/" class="md-nav__link">
<span class="md-ellipsis">
Scaling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/backup-restore/" class="md-nav__link">
<span class="md-ellipsis">
Backup & Restore
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_9" >
<div class="md-nav__link md-nav__container">
<a href="../../../development/" class="md-nav__link ">
<span class="md-ellipsis">
Development
</span>
</a>
<label class="md-nav__link " for="__nav_2_9" id="__nav_2_9_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_9_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_9">
<span class="md-nav__icon md-icon"></span>
Development
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../development/local-setup/" class="md-nav__link">
<span class="md-ellipsis">
Local Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/docker-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Docker Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/git-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Git Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/npm-commands/" class="md-nav__link">
<span class="md-ellipsis">
NPM Commands
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/typescript/" class="md-nav__link">
<span class="md-ellipsis">
TypeScript
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/debugging/" class="md-nav__link">
<span class="md-ellipsis">
Debugging
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/code-style/" class="md-nav__link">
<span class="md-ellipsis">
Code Style
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_10" >
<div class="md-nav__link md-nav__container">
<a href="../../../api-reference/" class="md-nav__link ">
<span class="md-ellipsis">
API Reference
</span>
</a>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_10_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_10">
<span class="md-nav__icon md-icon"></span>
API Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_11" >
<div class="md-nav__link md-nav__container">
<a href="../../../user-guides/" class="md-nav__link ">
<span class="md-ellipsis">
User Guides
</span>
</a>
<label class="md-nav__link " for="__nav_2_11" id="__nav_2_11_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_11_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_11">
<span class="md-nav__icon md-icon"></span>
User Guides
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../user-guides/admin-guide/" class="md-nav__link">
<span class="md-ellipsis">
Admin Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/campaign-manager-guide/" class="md-nav__link">
<span class="md-ellipsis">
Campaign Manager Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/map-organizer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Map Organizer Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/content-editor-guide/" class="md-nav__link">
<span class="md-ellipsis">
Content Editor Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/volunteer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Volunteer Guide
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_12" >
<div class="md-nav__link md-nav__container">
<a href="../../../troubleshooting/" class="md-nav__link ">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<label class="md-nav__link " for="__nav_2_12" id="__nav_2_12_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_12_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_12">
<span class="md-nav__icon md-icon"></span>
Troubleshooting
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../troubleshooting/faq/" class="md-nav__link">
<span class="md-ellipsis">
FAQ
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/common-errors/" class="md-nav__link">
<span class="md-ellipsis">
Common Errors
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/auth-issues/" class="md-nav__link">
<span class="md-ellipsis">
Auth Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/database-issues/" class="md-nav__link">
<span class="md-ellipsis">
Database Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/docker-issues/" class="md-nav__link">
<span class="md-ellipsis">
Docker Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/email-issues/" class="md-nav__link">
<span class="md-ellipsis">
Email Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/geocoding-issues/" class="md-nav__link">
<span class="md-ellipsis">
Geocoding Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/monitoring-issues/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/performance-optimization/" class="md-nav__link">
<span class="md-ellipsis">
Performance Optimization
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_13" >
<div class="md-nav__link md-nav__container">
<a href="../../../migration/" class="md-nav__link ">
<span class="md-ellipsis">
Migration
</span>
</a>
<label class="md-nav__link " for="__nav_2_13" id="__nav_2_13_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_13_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_13">
<span class="md-nav__icon md-icon"></span>
Migration
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../migration/feature-parity/" class="md-nav__link">
<span class="md-ellipsis">
Feature Parity
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/breaking-changes/" class="md-nav__link">
<span class="md-ellipsis">
Breaking Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/api-changes/" class="md-nav__link">
<span class="md-ellipsis">
API Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/data-migration/" class="md-nav__link">
<span class="md-ellipsis">
Data Migration
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_14" >
<div class="md-nav__link md-nav__container">
<a href="../../../contributing/" class="md-nav__link ">
<span class="md-ellipsis">
Contributing
</span>
</a>
<label class="md-nav__link " for="__nav_2_14" id="__nav_2_14_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_14_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_14">
<span class="md-nav__icon md-icon"></span>
Contributing
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../contributing/development-setup/" class="md-nav__link">
<span class="md-ellipsis">
Development Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/code-of-conduct/" class="md-nav__link">
<span class="md-ellipsis">
Code of Conduct
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/pull-requests/" class="md-nav__link">
<span class="md-ellipsis">
Pull Requests
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/roadmap/" class="md-nav__link">
<span class="md-ellipsis">
Roadmap
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../phil/" class="md-nav__link">
<span class="md-ellipsis">
Philosophy
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../v1/" class="md-nav__link">
<span class="md-ellipsis">
V1 Documentation (Legacy)
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../blog/" class="md-nav__link">
<span class="md-ellipsis">
Blog
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#architecture" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-models" class="md-nav__link">
<span class="md-ellipsis">
Database Models
</span>
</a>
<nav class="md-nav" aria-label="Database Models">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#location-model" class="md-nav__link">
<span class="md-ellipsis">
Location Model
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#address-model" class="md-nav__link">
<span class="md-ellipsis">
Address Model
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-endpoints" class="md-nav__link">
<span class="md-ellipsis">
API Endpoints
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
<nav class="md-nav" aria-label="Configuration">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#environment-variables" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-indexes" class="md-nav__link">
<span class="md-ellipsis">
Database Indexes
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#admin-workflow" class="md-nav__link">
<span class="md-ellipsis">
Admin Workflow
</span>
</a>
<nav class="md-nav" aria-label="Admin Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-location" class="md-nav__link">
<span class="md-ellipsis">
Creating a Location
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#csv-import-workflow" class="md-nav__link">
<span class="md-ellipsis">
CSV Import Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-server-import-workflow" class="md-nav__link">
<span class="md-ellipsis">
NAR Server Import Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#bulk-re-geocoding" class="md-nav__link">
<span class="md-ellipsis">
Bulk Re-Geocoding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#exporting-locations" class="md-nav__link">
<span class="md-ellipsis">
Exporting Locations
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#public-workflow" class="md-nav__link">
<span class="md-ellipsis">
Public Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#volunteer-workflow" class="md-nav__link">
<span class="md-ellipsis">
Volunteer Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-examples" class="md-nav__link">
<span class="md-ellipsis">
Code Examples
</span>
</a>
<nav class="md-nav" aria-label="Code Examples">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-location-frontend" class="md-nav__link">
<span class="md-ellipsis">
Creating a Location (Frontend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#geocoding-an-address-frontend" class="md-nav__link">
<span class="md-ellipsis">
Geocoding an Address (Frontend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#location-service-create-backend" class="md-nav__link">
<span class="md-ellipsis">
Location Service Create (Backend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#csv-import-detection-backend" class="md-nav__link">
<span class="md-ellipsis">
CSV Import Detection (Backend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-lambert-coordinate-conversion-backend" class="md-nav__link">
<span class="md-ellipsis">
NAR Lambert Coordinate Conversion (Backend)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#spatial-filtering-by-cut-backend" class="md-nav__link">
<span class="md-ellipsis">
Spatial Filtering by Cut (Backend)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<nav class="md-nav" aria-label="Troubleshooting">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#issue-geocoding-fails-for-valid-address" class="md-nav__link">
<span class="md-ellipsis">
Issue: Geocoding Fails for Valid Address
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#issue-nar-import-fails-or-hangs" class="md-nav__link">
<span class="md-ellipsis">
Issue: NAR Import Fails or Hangs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#issue-duplicate-locations-created-on-import" class="md-nav__link">
<span class="md-ellipsis">
Issue: Duplicate Locations Created on Import
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#issue-low-geocode-confidence-for-nar-data" class="md-nav__link">
<span class="md-ellipsis">
Issue: Low Geocode Confidence for NAR Data
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#performance-considerations" class="md-nav__link">
<span class="md-ellipsis">
Performance Considerations
</span>
</a>
<nav class="md-nav" aria-label="Performance Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#query-optimization" class="md-nav__link">
<span class="md-ellipsis">
Query Optimization
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#geocoding-rate-limits" class="md-nav__link">
<span class="md-ellipsis">
Geocoding Rate Limits
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-import-performance" class="md-nav__link">
<span class="md-ellipsis">
NAR Import Performance
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#map-rendering-performance" class="md-nav__link">
<span class="md-ellipsis">
Map Rendering Performance
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#related-documentation" class="md-nav__link">
<span class="md-ellipsis">
Related Documentation
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<nav class="md-path" aria-label="Navigation" >
<ol class="md-path__list">
<li class="md-path__item">
<a href="../../../.." class="md-path__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../../" class="md-path__link">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../" class="md-path__link">
<span class="md-ellipsis">
Features
</span>
</a>
</li>
<li class="md-path__item">
<a href="../" class="md-path__link">
<span class="md-ellipsis">
Map
</span>
</a>
</li>
</ol>
</nav>
<article class="md-content__inner md-typeset">
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/features/map/locations.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
</a>
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/features/map/locations.md" title="View source of this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 18c.56 0 1 .44 1 1s-.44 1-1 1-1-.44-1-1 .44-1 1-1m0-3c-2.73 0-5.06 1.66-6 4 .94 2.34 3.27 4 6 4s5.06-1.66 6-4c-.94-2.34-3.27-4-6-4m0 6.5a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5M9.27 20H6V4h7v5h5v4.07c.7.08 1.36.25 2 .49V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.5a8.2 8.2 0 0 1-1.23-2"/></svg>
</a>
<h1 id="location-management-system">Location Management System<a class="headerlink" href="#location-management-system" title="Permanent link">&para;</a></h1>
<h2 id="overview">Overview<a class="headerlink" href="#overview" title="Permanent link">&para;</a></h2>
<p>The location management system is the foundation of Changemaker Lite's field organizing capabilities. It provides building-level and unit-level voter/supporter tracking with comprehensive address management, geocoding integration, and Canadian electoral data (NAR) import support.</p>
<p><strong>Key Capabilities:</strong></p>
<ul>
<li><strong>Building + Unit Architecture</strong>: Location (building) has 1:N Address (units) for multi-unit buildings</li>
<li><strong>NAR Integration</strong>: Import Canadian electoral data (LOC_GUID, ADDR_GUID from Elections Canada)</li>
<li><strong>Multi-Provider Geocoding</strong>: Automatically geocode addresses with confidence scoring</li>
<li><strong>CSV Import/Export</strong>: Bulk operations for campaign data management</li>
<li><strong>Support Level Tracking</strong>: LEVEL_1 (Strong) → LEVEL_4 (Opposed) classification</li>
<li><strong>Spatial Filtering</strong>: Filter locations by polygon cuts or bounding box</li>
<li><strong>History Tracking</strong>: Complete audit trail of location changes</li>
<li><strong>Field Data</strong>: Sign tracking, building notes, federal district assignment</li>
</ul>
<p><strong>Use Cases:</strong></p>
<ul>
<li>Voter file management for electoral campaigns</li>
<li>Door-to-door canvassing organization</li>
<li>Sign placement tracking (lawn signs, window signs)</li>
<li>Multi-unit building canvassing (apartments, condos)</li>
<li>Federal electoral district mapping</li>
<li>NAR 2025 import for Canadian campaigns</li>
<li>Walk sheet generation for field teams</li>
</ul>
<h2 id="architecture">Architecture<a class="headerlink" href="#architecture" title="Permanent link">&para;</a></h2>
<pre class="mermaid"><code>graph TD
A[Admin User] --&gt;|Manages Locations| B[LocationsPage]
B --&gt;|CRUD Operations| C[Locations API]
C --&gt;|Save/Query| D[(Location Model)]
C --&gt;|Geocode Address| E[Geocoding Service]
E --&gt;|Try Providers| F[Multi-Provider Chain]
F --&gt;|Cache Result| G[(Redis Cache)]
H[CSV Import] --&gt;|Parse File| C
C --&gt;|Validate| I[Location Service]
I --&gt;|Auto-Geocode| E
I --&gt;|Create Records| D
J[NAR Import] --&gt;|Server Stream| K[NAR Import Service]
K --&gt;|Join Address+Location| L[Location Files]
K --&gt;|Convert Coords| M[proj4 Lambert→WGS84]
K --&gt;|Filter| N[Cut/City/Postal]
K --&gt;|Bulk Insert| D
D --&gt;|1:N| O[(Address Model)]
D --&gt;|Assigned To| P[(Cut Model)]
Q[Public Map] --&gt;|GET /api/public/map/locations| C
C --&gt;|Filter by Bounds| D
R[Canvass Session] --&gt;|Load Addresses| C
C --&gt;|Point-in-Polygon| S[Spatial Utils]
style D fill:#e1f5ff
style O fill:#e1f5ff
style P fill:#e1f5ff
style G fill:#fff4e1</code></pre>
<p><strong>Flow Description:</strong></p>
<ol>
<li><strong>Admin creates location</strong> → Location service validates address and optionally geocodes</li>
<li><strong>CSV import</strong> → Service parses file, detects format (standard/NAR), geocodes if needed, creates records</li>
<li><strong>NAR server import</strong> → Streams large files, joins Address+Location CSVs, converts Lambert coords, filters, bulk inserts</li>
<li><strong>Public map loads</strong> → Location service queries by bounds, returns color-coded markers</li>
<li><strong>Canvass session starts</strong> → Service loads addresses within cut polygon using ray-casting algorithm</li>
<li><strong>Geocoding</strong> → Multi-provider chain tries providers in order, caches successful results</li>
</ol>
<h2 id="database-models">Database Models<a class="headerlink" href="#database-models" title="Permanent link">&para;</a></h2>
<h3 id="location-model">Location Model<a class="headerlink" href="#location-model" title="Permanent link">&para;</a></h3>
<p>See <a href="../../../database/models/map/#location-model">Location Model Documentation</a> for full schema.</p>
<p><strong>Key Fields:</strong></p>
<ul>
<li><code>latitude</code> / <code>longitude</code>: WGS84 coordinates (Decimal type for precision)</li>
<li><code>address</code>: Street address (building level, not including unit numbers)</li>
<li><code>postalCode</code>: Canadian postal code (A1A 1A1 format)</li>
<li><code>province</code>: Province code (ON, QC, AB, etc.)</li>
<li><code>federalDistrict</code>: Federal electoral district name</li>
<li><code>buildingType</code>: SINGLE_FAMILY | MULTI_UNIT | MIXED_USE | COMMERCIAL</li>
<li><code>totalUnits</code>: Number of units in building (for multi-unit buildings)</li>
<li><code>geocodeConfidence</code>: Confidence score 0-100 from geocoding service</li>
<li><code>geocodeProvider</code>: Google, Mapbox, Nominatim, Photon, LocationIQ, ArcGIS</li>
<li><code>narLocGuid</code>: NAR LOC_GUID identifier (Canadian electoral data)</li>
<li><code>buildingNotes</code>: Free-text notes about building access, parking, etc.</li>
</ul>
<p><strong>NAR-Specific Fields:</strong></p>
<ul>
<li><code>narLocGuid</code>: Location GUID from NAR dataset</li>
<li><code>buildingUse</code>: Building use code (1=Residential, 2=Commercial, etc.)</li>
<li><code>postalCode</code>: Extracted from NAR MAIL_POSTAL_CODE</li>
<li><code>province</code>: Extracted from NAR PROV_CODE</li>
<li><code>federalDistrict</code>: Extracted from NAR FED_ENG_NAME</li>
</ul>
<p><strong>Geocoding Fields:</strong></p>
<ul>
<li><code>geocodeConfidence</code>: 0-100 score (&gt;90=high, 70-90=medium, &lt;70=low)</li>
<li><code>geocodeProvider</code>: Which provider successfully geocoded the address</li>
<li><code>geocodeAttempts</code>: Number of failed geocoding attempts</li>
<li><code>lastGeocodeAttempt</code>: Timestamp of last geocoding attempt</li>
</ul>
<h3 id="address-model">Address Model<a class="headerlink" href="#address-model" title="Permanent link">&para;</a></h3>
<p>See <a href="../../../database/models/map/#address-model">Address Model Documentation</a> for full schema.</p>
<p><strong>Key Fields:</strong></p>
<ul>
<li><code>locationId</code>: Foreign key to Location (building)</li>
<li><code>unitNumber</code>: Unit/apartment/suite number (optional for single-family)</li>
<li><code>firstName</code> / <code>lastName</code>: Resident name</li>
<li><code>email</code> / <code>phone</code>: Contact information</li>
<li><code>supportLevel</code>: LEVEL_1 (Strong) | LEVEL_2 (Leaning) | LEVEL_3 (Undecided) | LEVEL_4 (Opposed)</li>
<li><code>sign</code>: Boolean - has lawn/window sign</li>
<li><code>signSize</code>: Sign size description (e.g., "24x18 lawn", "window")</li>
<li><code>notes</code>: Free-text notes from canvassing</li>
<li><code>narAddrGuid</code>: NAR ADDR_GUID identifier</li>
</ul>
<p><strong>NAR-Specific Fields:</strong></p>
<ul>
<li><code>narAddrGuid</code>: Address GUID from NAR dataset</li>
<li><code>unitNumber</code>: Extracted from NAR APT_NO_LABEL</li>
</ul>
<p><strong>Related Models:</strong></p>
<ul>
<li><a href="../../../database/models/map/#cut-model">Cut</a> — Polygon overlays for organizing</li>
<li><a href="../../../database/models/canvass/#canvassvisit-model">CanvassVisit</a> — Door-knock records</li>
<li><a href="../../../database/models/map/#locationhistory-model">LocationHistory</a> — Audit trail</li>
</ul>
<h2 id="api-endpoints">API Endpoints<a class="headerlink" href="#api-endpoints" title="Permanent link">&para;</a></h2>
<p>See <a href="../../backend/modules/map/locations.md">Locations Backend Module Documentation</a> for full API reference.</p>
<p><strong>Admin Endpoints:</strong></p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Endpoint</th>
<th>Auth</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/api/map/locations</code></td>
<td>MAP_ADMIN</td>
<td>List locations with pagination, search, filters</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/map/locations/stats</code></td>
<td>MAP_ADMIN</td>
<td>Get location statistics (total, geocoded, by confidence)</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/map/locations/:id</code></td>
<td>MAP_ADMIN</td>
<td>Get location details with addresses</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/map/locations</code></td>
<td>MAP_ADMIN</td>
<td>Create new location</td>
</tr>
<tr>
<td>PATCH</td>
<td><code>/api/map/locations/:id</code></td>
<td>MAP_ADMIN</td>
<td>Update location</td>
</tr>
<tr>
<td>DELETE</td>
<td><code>/api/map/locations/:id</code></td>
<td>MAP_ADMIN</td>
<td>Delete location (and cascade addresses)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/map/locations/geocode</code></td>
<td>MAP_ADMIN</td>
<td>Geocode single address</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/map/locations/reverse-geocode</code></td>
<td>MAP_ADMIN</td>
<td>Reverse geocode lat/lng to address</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/map/locations/import</code></td>
<td>MAP_ADMIN</td>
<td>Import CSV file (standard or NAR format)</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/map/locations/export</code></td>
<td>MAP_ADMIN</td>
<td>Export locations to CSV</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/map/locations/:id/history</code></td>
<td>MAP_ADMIN</td>
<td>Get location change history</td>
</tr>
</tbody>
</table>
<p><strong>Bulk Operations:</strong></p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Endpoint</th>
<th>Auth</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>POST</td>
<td><code>/api/map/locations/bulk-geocode/start</code></td>
<td>MAP_ADMIN</td>
<td>Start bulk geocoding job (BullMQ)</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/map/locations/bulk-geocode/status</code></td>
<td>MAP_ADMIN</td>
<td>Check bulk geocoding job status</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/map/locations/bulk-geocode/cancel</code></td>
<td>MAP_ADMIN</td>
<td>Cancel running bulk geocoding job</td>
</tr>
</tbody>
</table>
<p><strong>NAR Import Endpoints:</strong></p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Endpoint</th>
<th>Auth</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/api/map/locations/nar/datasets</code></td>
<td>MAP_ADMIN</td>
<td>List available NAR datasets from <code>/data</code> directory</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/map/locations/nar/import</code></td>
<td>MAP_ADMIN</td>
<td>Server-side streaming NAR import with filters</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/map/locations/nar/import/progress</code></td>
<td>MAP_ADMIN</td>
<td>Get NAR import progress (polling endpoint)</td>
</tr>
</tbody>
</table>
<p><strong>Public Endpoints:</strong></p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Endpoint</th>
<th>Auth</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/api/public/map/locations</code></td>
<td>None</td>
<td>List locations by bounds (for public map)</td>
</tr>
</tbody>
</table>
<p><strong>Volunteer Endpoints:</strong></p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Endpoint</th>
<th>Auth</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>PATCH</td>
<td><code>/api/map/canvass/volunteer/locations/:id</code></td>
<td>Any logged-in user</td>
<td>Update location from canvass session</td>
</tr>
</tbody>
</table>
<h2 id="configuration">Configuration<a class="headerlink" href="#configuration" title="Permanent link">&para;</a></h2>
<h3 id="environment-variables">Environment Variables<a class="headerlink" href="#environment-variables" title="Permanent link">&para;</a></h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GEOCODING_ENABLED</code></td>
<td>boolean</td>
<td><code>true</code></td>
<td>Enable geocoding services</td>
</tr>
<tr>
<td><code>GEOCODING_CACHE_ENABLED</code></td>
<td>boolean</td>
<td><code>true</code></td>
<td>Cache geocoding results in Redis</td>
</tr>
<tr>
<td><code>GEOCODING_CACHE_TTL_HOURS</code></td>
<td>number</td>
<td><code>168</code></td>
<td>Cache TTL (7 days)</td>
</tr>
<tr>
<td><code>GEOCODING_PROVIDERS</code></td>
<td>string[]</td>
<td>See geocoding.md</td>
<td>Comma-separated provider list</td>
</tr>
<tr>
<td><code>GOOGLE_MAPS_API_KEY</code></td>
<td>string</td>
<td>-</td>
<td>Google Geocoding API key</td>
</tr>
<tr>
<td><code>MAPBOX_ACCESS_TOKEN</code></td>
<td>string</td>
<td>-</td>
<td>Mapbox API token</td>
</tr>
<tr>
<td><code>LOCATIONIQ_API_KEY</code></td>
<td>string</td>
<td>-</td>
<td>LocationIQ API key</td>
</tr>
<tr>
<td><code>NAR_DATA_DIR</code></td>
<td>string</td>
<td><code>/data</code></td>
<td>Directory containing NAR CSV files</td>
</tr>
</tbody>
</table>
<h3 id="database-indexes">Database Indexes<a class="headerlink" href="#database-indexes" title="Permanent link">&para;</a></h3>
<p>Key indexes for performance:</p>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1">-- Location queries</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_lat_lng</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">);</span>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_postal_code</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="ss">&quot;postalCode&quot;</span><span class="p">);</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_province</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="n">province</span><span class="p">);</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_federal_district</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="ss">&quot;federalDistrict&quot;</span><span class="p">);</span>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_geocode_confidence</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="ss">&quot;geocodeConfidence&quot;</span><span class="p">);</span>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_nar_loc_guid</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="ss">&quot;narLocGuid&quot;</span><span class="p">);</span>
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a>
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="c1">-- Address queries</span>
</span><span id="__span-0-10"><a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_addresses_location_id</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Address&quot;</span><span class="w"> </span><span class="p">(</span><span class="ss">&quot;locationId&quot;</span><span class="p">);</span>
</span><span id="__span-0-11"><a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_addresses_support_level</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Address&quot;</span><span class="w"> </span><span class="p">(</span><span class="ss">&quot;supportLevel&quot;</span><span class="p">);</span>
</span><span id="__span-0-12"><a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_addresses_nar_addr_guid</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Address&quot;</span><span class="w"> </span><span class="p">(</span><span class="ss">&quot;narAddrGuid&quot;</span><span class="p">);</span>
</span><span id="__span-0-13"><a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a>
</span><span id="__span-0-14"><a id="__codelineno-0-14" name="__codelineno-0-14" href="#__codelineno-0-14"></a><span class="c1">-- Spatial queries (cut assignment)</span>
</span><span id="__span-0-15"><a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_lat</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="n">latitude</span><span class="p">);</span>
</span><span id="__span-0-16"><a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_locations_lng</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span><span class="w"> </span><span class="p">(</span><span class="n">longitude</span><span class="p">);</span>
</span></code></pre></div>
<h2 id="admin-workflow">Admin Workflow<a class="headerlink" href="#admin-workflow" title="Permanent link">&para;</a></h2>
<h3 id="creating-a-location">Creating a Location<a class="headerlink" href="#creating-a-location" title="Permanent link">&para;</a></h3>
<p><strong>Step 1: Navigate to Locations Page</strong></p>
<p>Navigate to <strong>Map → Locations</strong> in the admin sidebar.</p>
<p>![LocationsPage Screenshot Placeholder]</p>
<p><strong>Step 2: Click "Add Location"</strong></p>
<p>Click the <strong>+ Add Location</strong> button in the top-right corner.</p>
<p><strong>Step 3: Enter Address Information</strong></p>
<p>Fill in the location form:</p>
<ul>
<li><strong>Address</strong>: Street address (e.g., "123 Main Street")</li>
<li><strong>Postal Code</strong>: Canadian postal code (e.g., "K1A 0B1")</li>
<li><strong>Building Type</strong>: Single Family / Multi-Unit / Mixed Use / Commercial</li>
<li><strong>Total Units</strong>: Number of units (for multi-unit buildings)</li>
<li><strong>Building Notes</strong>: Access codes, parking info, etc.</li>
</ul>
<p><strong>Step 4: Auto-Geocode (Optional)</strong></p>
<p>Click <strong>Geocode</strong> button to automatically fetch latitude/longitude coordinates. The system will:</p>
<ol>
<li>Try geocoding providers in order (Google → Mapbox → Nominatim → Photon → LocationIQ → ArcGIS)</li>
<li>Return confidence score (0-100)</li>
<li>Display formatted address from provider</li>
<li>Cache result in Redis for 7 days</li>
</ol>
<p><strong>Step 5: Add Addresses (Units)</strong></p>
<p>For multi-unit buildings, click <strong>Add Address</strong> to create unit records:</p>
<ul>
<li><strong>Unit Number</strong>: Apartment/suite number</li>
<li><strong>First Name / Last Name</strong>: Resident name</li>
<li><strong>Support Level</strong>: LEVEL_1 (Strong) → LEVEL_4 (Opposed)</li>
<li><strong>Sign</strong>: Check if resident has lawn/window sign</li>
<li><strong>Notes</strong>: Canvassing notes</li>
</ul>
<p><strong>Step 6: Save Location</strong></p>
<p>Click <strong>Create</strong> to save the location and addresses.</p>
<h3 id="csv-import-workflow">CSV Import Workflow<a class="headerlink" href="#csv-import-workflow" title="Permanent link">&para;</a></h3>
<p><strong>Step 1: Prepare CSV File</strong></p>
<p>Prepare a CSV file with the following columns (flexible header names):</p>
<p><strong>Standard Format:</strong></p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a>address,firstName,lastName,email,phone,unitNumber,supportLevel,sign,notes,latitude,longitude
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a>123 Main St,John,Doe,john@example.com,555-1234,101,LEVEL_1,true,Friendly contact,,
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a>124 Main St,Jane,Smith,jane@example.com,555-5678,,LEVEL_2,false,Ask about lawn sign,45.4215,-75.6972
</span></code></pre></div>
<p><strong>NAR Format</strong> (auto-detected if 3+ NAR columns present):</p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a>CIVIC_NO,OFFICIAL_STREET_NAME,OFFICIAL_STREET_TYPE,APT_NO_LABEL,MAIL_POSTAL_CODE,BG_LATITUDE,BG_LONGITUDE,FED_ENG_NAME
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2" href="#__codelineno-2-2"></a>123,Main,Street,101,K1A 0B1,45.4215,-75.6972,Ottawa Centre
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3" href="#__codelineno-2-3"></a>124,Main,Street,,K1A 0B2,45.4220,-75.6975,Ottawa Centre
</span></code></pre></div>
<p><strong>Step 2: Open Import Modal</strong></p>
<p>Click <strong>Import CSV</strong> button on LocationsPage.</p>
<p><strong>Step 3: Select Import Format</strong></p>
<p>Choose format:</p>
<ul>
<li><strong>Standard</strong>: General campaign CSV (address, firstName, lastName, supportLevel, etc.)</li>
<li><strong>NAR</strong>: National Address Register format (auto-detected)</li>
<li><strong>Server</strong>: Server-side NAR streaming import (for large files &gt;100MB)</li>
</ul>
<p><strong>Step 4: Configure Filters (Optional)</strong></p>
<p>Filter imported locations:</p>
<ul>
<li><strong>Cut</strong>: Import only locations within a polygon</li>
<li><strong>Map Area</strong>: Import only locations within current map bounds</li>
<li><strong>City</strong>: Filter by city name</li>
<li><strong>Province</strong>: Filter by province code (ON, QC, AB, etc.)</li>
<li><strong>Residential Only</strong>: Exclude commercial buildings (BU_USE = 1)</li>
</ul>
<p><strong>Step 5: Upload File</strong></p>
<p>Drag-and-drop or click to select CSV file.</p>
<p><strong>Step 6: Configure Geocoding</strong></p>
<p>Toggle <strong>Geocode Missing Coordinates</strong>:</p>
<ul>
<li><strong>Enabled</strong>: Automatically geocode addresses without lat/lng (slower, uses geocoding API quota)</li>
<li><strong>Disabled</strong>: Import only records with coordinates (faster, for NAR imports)</li>
</ul>
<p><strong>Step 7: Review Import Results</strong></p>
<p>After import completes, view results:</p>
<ul>
<li><strong>Created</strong>: Number of new locations created</li>
<li><strong>Skipped</strong>: Number of duplicate addresses skipped</li>
<li><strong>Failed</strong>: Number of errors (invalid addresses, geocoding failures)</li>
<li><strong>Geocoded</strong>: Number of addresses successfully geocoded</li>
</ul>
<h3 id="nar-server-import-workflow">NAR Server Import Workflow<a class="headerlink" href="#nar-server-import-workflow" title="Permanent link">&para;</a></h3>
<p><strong>For large NAR datasets (&gt;100MB), use server-side streaming import:</strong></p>
<p><strong>Step 1: Upload NAR Files to Server</strong></p>
<p>Copy NAR CSV files to server's <code>/data</code> directory:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="c1"># Example NAR files for Ontario (province code 35)</span>
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2" href="#__codelineno-3-2"></a>/data/Address_35_part_1.csv
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3" href="#__codelineno-3-3"></a>/data/Address_35_part_2.csv
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4" href="#__codelineno-3-4"></a>/data/Location_35.csv
</span></code></pre></div>
<p><strong>Step 2: Open NAR Import Tab</strong></p>
<p>Click <strong>NAR Import</strong> tab on LocationsPage.</p>
<p><strong>Step 3: Scan for Datasets</strong></p>
<p>Click <strong>Scan NAR Directory</strong> to detect available datasets. The system will:</p>
<ul>
<li>Scan <code>/data</code> directory for Address_<em>.csv and Location_</em>.csv files</li>
<li>Group files by province code (10=NL, 24=QC, 35=ON, 48=AB, etc.)</li>
<li>Display file sizes and counts</li>
</ul>
<p><strong>Step 4: Select Province</strong></p>
<p>Choose province from dropdown (e.g., "35 - Ontario (10.5 GB, 45 files)").</p>
<p><strong>Step 5: Configure Filters</strong></p>
<p>Apply optional filters:</p>
<ul>
<li><strong>City</strong>: Filter by MAIL_MUN_NAME or CSD_ENG_NAME</li>
<li><strong>Postal Code Prefix</strong>: Filter by first 3 characters (e.g., "K1A")</li>
<li><strong>Cut</strong>: Import only addresses within polygon</li>
<li><strong>Residential Only</strong>: Exclude commercial buildings (BU_USE != 1)</li>
</ul>
<p><strong>Step 6: Start Import</strong></p>
<p>Click <strong>Start Import</strong>. The system will:</p>
<ol>
<li>Stream Address CSV files (multi-part files processed sequentially)</li>
<li>Join with Location CSV on LOC_GUID</li>
<li>Convert BG_X/BG_Y (Lambert projection) to lat/lng (WGS84) using proj4</li>
<li>Apply filters (city, postal, cut, residential)</li>
<li>Bulk insert locations + addresses (transaction batches of 500)</li>
<li>Update progress every 5 seconds</li>
</ol>
<p><strong>Step 7: Monitor Progress</strong></p>
<p>View real-time progress:</p>
<ul>
<li><strong>Records Processed</strong>: Current/total count</li>
<li><strong>Progress Percentage</strong>: Visual progress bar</li>
<li><strong>ETA</strong>: Estimated time remaining</li>
<li><strong>Current File</strong>: Which multi-part file is being processed</li>
</ul>
<p><strong>Step 8: Review Results</strong></p>
<p>After import completes:</p>
<ul>
<li><strong>Total Created</strong>: Number of locations + addresses created</li>
<li><strong>Duration</strong>: Total import time</li>
<li><strong>Skipped</strong>: Duplicate or filtered records</li>
</ul>
<h3 id="bulk-re-geocoding">Bulk Re-Geocoding<a class="headerlink" href="#bulk-re-geocoding" title="Permanent link">&para;</a></h3>
<p><strong>For locations with missing or low-confidence coordinates:</strong></p>
<p><strong>Step 1: Open Bulk Geocode Modal</strong></p>
<p>Click <strong>Bulk Re-Geocode</strong> button on LocationsPage.</p>
<p><strong>Step 2: Configure Job Parameters</strong></p>
<p>Set parameters:</p>
<ul>
<li><strong>Confidence Filter</strong>: Re-geocode locations below threshold (e.g., &lt;70)</li>
<li><strong>Missing Only</strong>: Only geocode locations without coordinates</li>
<li><strong>Provider</strong>: Choose preferred geocoding provider</li>
<li><strong>Batch Size</strong>: Number of locations per batch (default: 50)</li>
</ul>
<p><strong>Step 3: Start Job</strong></p>
<p>Click <strong>Start Job</strong> to queue bulk geocoding job in BullMQ.</p>
<p><strong>Step 4: Monitor Progress</strong></p>
<p>Poll job status:</p>
<ul>
<li><strong>Completed</strong>: Number of successfully geocoded locations</li>
<li><strong>Failed</strong>: Number of geocoding failures</li>
<li><strong>Progress</strong>: Percentage complete</li>
<li><strong>ETA</strong>: Estimated time remaining</li>
</ul>
<p><strong>Step 5: Cancel Job (Optional)</strong></p>
<p>Click <strong>Cancel Job</strong> to stop bulk geocoding.</p>
<h3 id="exporting-locations">Exporting Locations<a class="headerlink" href="#exporting-locations" title="Permanent link">&para;</a></h3>
<p><strong>Step 1: Configure Export Filters</strong></p>
<p>Apply filters on LocationsPage:</p>
<ul>
<li><strong>Search</strong>: Filter by address or notes</li>
<li><strong>Confidence Level</strong>: High / Medium / Low / None</li>
<li><strong>Cut</strong>: Export locations within specific polygon</li>
</ul>
<p><strong>Step 2: Click Export CSV</strong></p>
<p>Click <strong>Export CSV</strong> button. The system will:</p>
<ol>
<li>Export locations matching current filters</li>
<li>Include all address records (one row per address)</li>
<li>Download CSV file with timestamp</li>
</ol>
<p><strong>Export Format:</strong></p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a>locationId,address,latitude,longitude,postalCode,province,federalDistrict,buildingType,totalUnits,geocodeConfidence,geocodeProvider,unitNumber,firstName,lastName,email,phone,supportLevel,sign,signSize,notes
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a>uuid-1,123 Main St,45.4215,-75.6972,K1A 0B1,ON,Ottawa Centre,MULTI_UNIT,12,95,GOOGLE,101,John,Doe,john@example.com,555-1234,LEVEL_1,true,24x18 lawn,Friendly contact
</span></code></pre></div>
<h2 id="public-workflow">Public Workflow<a class="headerlink" href="#public-workflow" title="Permanent link">&para;</a></h2>
<p><strong>Public users can view locations on the interactive map.</strong></p>
<p><strong>Step 1: Navigate to Public Map</strong></p>
<p>Visit <code>/map</code> (public route, no authentication required).</p>
<p><strong>Step 2: Browse Map</strong></p>
<p>Interact with Leaflet map:</p>
<ul>
<li><strong>Zoom/Pan</strong>: Use mouse or touch gestures</li>
<li><strong>Markers</strong>: Locations displayed as color-coded circle markers:</li>
<li><strong>Green</strong>: LEVEL_1 (Strong support)</li>
<li><strong>Yellow</strong>: LEVEL_2 (Leaning support)</li>
<li><strong>Gray</strong>: LEVEL_3 (Undecided)</li>
<li><strong>Red</strong>: LEVEL_4 (Opposed)</li>
<li><strong>Blue</strong>: No support level assigned</li>
</ul>
<p><strong>Step 3: View Cut Overlays</strong></p>
<p>Toggle cut overlays using <strong>Cuts</strong> control panel:</p>
<ul>
<li><strong>Show/Hide</strong>: Toggle cut visibility</li>
<li><strong>Opacity</strong>: Adjust polygon transparency</li>
<li><strong>Legend</strong>: View cut color legend</li>
</ul>
<p><strong>Step 4: Geolocate</strong></p>
<p>Click <strong>Geolocate</strong> button to center map on current location (requires browser geolocation permission).</p>
<p><strong>Step 5: Fullscreen Mode</strong></p>
<p>Click <strong>Fullscreen</strong> button to expand map to full screen.</p>
<h2 id="volunteer-workflow">Volunteer Workflow<a class="headerlink" href="#volunteer-workflow" title="Permanent link">&para;</a></h2>
<p><strong>Volunteers can update location data during canvassing sessions.</strong></p>
<p><strong>Step 1: Start Canvass Session</strong></p>
<p>See <a href="../canvassing/">Canvassing Documentation</a> for full workflow.</p>
<p><strong>Step 2: Record Visit</strong></p>
<p>When visiting a location, update fields:</p>
<ul>
<li><strong>Support Level</strong>: Update based on conversation</li>
<li><strong>Sign</strong>: Check if resident wants lawn/window sign</li>
<li><strong>Notes</strong>: Add canvassing notes</li>
</ul>
<p><strong>Step 3: Update Location</strong></p>
<p>Click <strong>Save Visit</strong> to record changes. The system will:</p>
<ol>
<li>Create CanvassVisit record with outcome</li>
<li>Update Address with new supportLevel/sign/notes</li>
<li>Update Location.lastUpdated timestamp</li>
<li>Create LocationHistory audit record</li>
</ol>
<h2 id="code-examples">Code Examples<a class="headerlink" href="#code-examples" title="Permanent link">&para;</a></h2>
<h3 id="creating-a-location-frontend">Creating a Location (Frontend)<a class="headerlink" href="#creating-a-location-frontend" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="c1">// admin/src/pages/LocationsPage.tsx</span>
</span><span id="__span-5-2"><a id="__codelineno-5-2" name="__codelineno-5-2" href="#__codelineno-5-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">handleCreate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">(</span><span class="nx">values</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-5-3"><a id="__codelineno-5-3" name="__codelineno-5-3" href="#__codelineno-5-3"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-5-4"><a id="__codelineno-5-4" name="__codelineno-5-4" href="#__codelineno-5-4"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">api</span><span class="p">.</span><span class="nx">post</span><span class="o">&lt;</span><span class="nx">Location</span><span class="o">&gt;</span><span class="p">(</span><span class="s1">&#39;/map/locations&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-5-5"><a id="__codelineno-5-5" name="__codelineno-5-5" href="#__codelineno-5-5"></a><span class="w"> </span><span class="nx">address</span><span class="o">:</span><span class="w"> </span><span class="kt">values.address</span><span class="p">,</span>
</span><span id="__span-5-6"><a id="__codelineno-5-6" name="__codelineno-5-6" href="#__codelineno-5-6"></a><span class="w"> </span><span class="nx">postalCode</span><span class="o">:</span><span class="w"> </span><span class="kt">values.postalCode</span><span class="p">,</span>
</span><span id="__span-5-7"><a id="__codelineno-5-7" name="__codelineno-5-7" href="#__codelineno-5-7"></a><span class="w"> </span><span class="nx">buildingType</span><span class="o">:</span><span class="w"> </span><span class="kt">values.buildingType</span><span class="p">,</span>
</span><span id="__span-5-8"><a id="__codelineno-5-8" name="__codelineno-5-8" href="#__codelineno-5-8"></a><span class="w"> </span><span class="nx">totalUnits</span><span class="o">:</span><span class="w"> </span><span class="kt">values.totalUnits</span><span class="p">,</span>
</span><span id="__span-5-9"><a id="__codelineno-5-9" name="__codelineno-5-9" href="#__codelineno-5-9"></a><span class="w"> </span><span class="nx">buildingNotes</span><span class="o">:</span><span class="w"> </span><span class="kt">values.buildingNotes</span><span class="p">,</span>
</span><span id="__span-5-10"><a id="__codelineno-5-10" name="__codelineno-5-10" href="#__codelineno-5-10"></a><span class="w"> </span><span class="nx">latitude</span><span class="o">:</span><span class="w"> </span><span class="kt">values.latitude</span><span class="p">,</span>
</span><span id="__span-5-11"><a id="__codelineno-5-11" name="__codelineno-5-11" href="#__codelineno-5-11"></a><span class="w"> </span><span class="nx">longitude</span><span class="o">:</span><span class="w"> </span><span class="kt">values.longitude</span><span class="p">,</span>
</span><span id="__span-5-12"><a id="__codelineno-5-12" name="__codelineno-5-12" href="#__codelineno-5-12"></a><span class="w"> </span><span class="nx">geocodeConfidence</span><span class="o">:</span><span class="w"> </span><span class="kt">values.geocodeConfidence</span><span class="p">,</span>
</span><span id="__span-5-13"><a id="__codelineno-5-13" name="__codelineno-5-13" href="#__codelineno-5-13"></a><span class="w"> </span><span class="nx">geocodeProvider</span><span class="o">:</span><span class="w"> </span><span class="kt">values.geocodeProvider</span><span class="p">,</span>
</span><span id="__span-5-14"><a id="__codelineno-5-14" name="__codelineno-5-14" href="#__codelineno-5-14"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-5-15"><a id="__codelineno-5-15" name="__codelineno-5-15" href="#__codelineno-5-15"></a>
</span><span id="__span-5-16"><a id="__codelineno-5-16" name="__codelineno-5-16" href="#__codelineno-5-16"></a><span class="w"> </span><span class="nx">message</span><span class="p">.</span><span class="nx">success</span><span class="p">(</span><span class="s1">&#39;Location created&#39;</span><span class="p">);</span>
</span><span id="__span-5-17"><a id="__codelineno-5-17" name="__codelineno-5-17" href="#__codelineno-5-17"></a><span class="w"> </span><span class="nx">setCreateModalOpen</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span><span id="__span-5-18"><a id="__codelineno-5-18" name="__codelineno-5-18" href="#__codelineno-5-18"></a><span class="w"> </span><span class="nx">createForm</span><span class="p">.</span><span class="nx">resetFields</span><span class="p">();</span>
</span><span id="__span-5-19"><a id="__codelineno-5-19" name="__codelineno-5-19" href="#__codelineno-5-19"></a><span class="w"> </span><span class="nx">fetchLocations</span><span class="p">();</span>
</span><span id="__span-5-20"><a id="__codelineno-5-20" name="__codelineno-5-20" href="#__codelineno-5-20"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-5-21"><a id="__codelineno-5-21" name="__codelineno-5-21" href="#__codelineno-5-21"></a><span class="w"> </span><span class="nx">message</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Failed to create location&#39;</span><span class="p">);</span>
</span><span id="__span-5-22"><a id="__codelineno-5-22" name="__codelineno-5-22" href="#__codelineno-5-22"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-5-23"><a id="__codelineno-5-23" name="__codelineno-5-23" href="#__codelineno-5-23"></a><span class="p">};</span>
</span></code></pre></div>
<h3 id="geocoding-an-address-frontend">Geocoding an Address (Frontend)<a class="headerlink" href="#geocoding-an-address-frontend" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a><span class="c1">// admin/src/pages/LocationsPage.tsx</span>
</span><span id="__span-6-2"><a id="__codelineno-6-2" name="__codelineno-6-2" href="#__codelineno-6-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">handleGeocode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-6-3"><a id="__codelineno-6-3" name="__codelineno-6-3" href="#__codelineno-6-3"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">address</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">createForm</span><span class="p">.</span><span class="nx">getFieldValue</span><span class="p">(</span><span class="s1">&#39;address&#39;</span><span class="p">);</span>
</span><span id="__span-6-4"><a id="__codelineno-6-4" name="__codelineno-6-4" href="#__codelineno-6-4"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">postalCode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">createForm</span><span class="p">.</span><span class="nx">getFieldValue</span><span class="p">(</span><span class="s1">&#39;postalCode&#39;</span><span class="p">);</span>
</span><span id="__span-6-5"><a id="__codelineno-6-5" name="__codelineno-6-5" href="#__codelineno-6-5"></a>
</span><span id="__span-6-6"><a id="__codelineno-6-6" name="__codelineno-6-6" href="#__codelineno-6-6"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">address</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-6-7"><a id="__codelineno-6-7" name="__codelineno-6-7" href="#__codelineno-6-7"></a><span class="w"> </span><span class="nx">message</span><span class="p">.</span><span class="nx">warning</span><span class="p">(</span><span class="s1">&#39;Please enter an address first&#39;</span><span class="p">);</span>
</span><span id="__span-6-8"><a id="__codelineno-6-8" name="__codelineno-6-8" href="#__codelineno-6-8"></a><span class="w"> </span><span class="k">return</span><span class="p">;</span>
</span><span id="__span-6-9"><a id="__codelineno-6-9" name="__codelineno-6-9" href="#__codelineno-6-9"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-6-10"><a id="__codelineno-6-10" name="__codelineno-6-10" href="#__codelineno-6-10"></a>
</span><span id="__span-6-11"><a id="__codelineno-6-11" name="__codelineno-6-11" href="#__codelineno-6-11"></a><span class="w"> </span><span class="nx">setGeocoding</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span><span id="__span-6-12"><a id="__codelineno-6-12" name="__codelineno-6-12" href="#__codelineno-6-12"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-6-13"><a id="__codelineno-6-13" name="__codelineno-6-13" href="#__codelineno-6-13"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">fullAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">postalCode</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="sb">`</span><span class="si">${</span><span class="nx">address</span><span class="si">}</span><span class="sb">, </span><span class="si">${</span><span class="nx">postalCode</span><span class="si">}</span><span class="sb">`</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="nx">address</span><span class="p">;</span>
</span><span id="__span-6-14"><a id="__codelineno-6-14" name="__codelineno-6-14" href="#__codelineno-6-14"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">api</span><span class="p">.</span><span class="nx">post</span><span class="o">&lt;</span><span class="nx">GeocodeResult</span><span class="o">&gt;</span><span class="p">(</span><span class="s1">&#39;/map/locations/geocode&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-6-15"><a id="__codelineno-6-15" name="__codelineno-6-15" href="#__codelineno-6-15"></a><span class="w"> </span><span class="nx">address</span><span class="o">:</span><span class="w"> </span><span class="kt">fullAddress</span><span class="p">,</span>
</span><span id="__span-6-16"><a id="__codelineno-6-16" name="__codelineno-6-16" href="#__codelineno-6-16"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-6-17"><a id="__codelineno-6-17" name="__codelineno-6-17" href="#__codelineno-6-17"></a>
</span><span id="__span-6-18"><a id="__codelineno-6-18" name="__codelineno-6-18" href="#__codelineno-6-18"></a><span class="w"> </span><span class="nx">createForm</span><span class="p">.</span><span class="nx">setFieldsValue</span><span class="p">({</span>
</span><span id="__span-6-19"><a id="__codelineno-6-19" name="__codelineno-6-19" href="#__codelineno-6-19"></a><span class="w"> </span><span class="nx">latitude</span><span class="o">:</span><span class="w"> </span><span class="kt">data.latitude</span><span class="p">,</span>
</span><span id="__span-6-20"><a id="__codelineno-6-20" name="__codelineno-6-20" href="#__codelineno-6-20"></a><span class="w"> </span><span class="nx">longitude</span><span class="o">:</span><span class="w"> </span><span class="kt">data.longitude</span><span class="p">,</span>
</span><span id="__span-6-21"><a id="__codelineno-6-21" name="__codelineno-6-21" href="#__codelineno-6-21"></a><span class="w"> </span><span class="nx">geocodeConfidence</span><span class="o">:</span><span class="w"> </span><span class="kt">data.confidence</span><span class="p">,</span>
</span><span id="__span-6-22"><a id="__codelineno-6-22" name="__codelineno-6-22" href="#__codelineno-6-22"></a><span class="w"> </span><span class="nx">geocodeProvider</span><span class="o">:</span><span class="w"> </span><span class="kt">data.provider</span><span class="p">,</span>
</span><span id="__span-6-23"><a id="__codelineno-6-23" name="__codelineno-6-23" href="#__codelineno-6-23"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-6-24"><a id="__codelineno-6-24" name="__codelineno-6-24" href="#__codelineno-6-24"></a>
</span><span id="__span-6-25"><a id="__codelineno-6-25" name="__codelineno-6-25" href="#__codelineno-6-25"></a><span class="w"> </span><span class="nx">message</span><span class="p">.</span><span class="nx">success</span><span class="p">(</span>
</span><span id="__span-6-26"><a id="__codelineno-6-26" name="__codelineno-6-26" href="#__codelineno-6-26"></a><span class="w"> </span><span class="sb">`Geocoded with </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">provider</span><span class="si">}</span><span class="sb"> (confidence: </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">confidence</span><span class="si">}</span><span class="sb">%)`</span>
</span><span id="__span-6-27"><a id="__codelineno-6-27" name="__codelineno-6-27" href="#__codelineno-6-27"></a><span class="w"> </span><span class="p">);</span>
</span><span id="__span-6-28"><a id="__codelineno-6-28" name="__codelineno-6-28" href="#__codelineno-6-28"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-6-29"><a id="__codelineno-6-29" name="__codelineno-6-29" href="#__codelineno-6-29"></a><span class="w"> </span><span class="nx">message</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Geocoding failed&#39;</span><span class="p">);</span>
</span><span id="__span-6-30"><a id="__codelineno-6-30" name="__codelineno-6-30" href="#__codelineno-6-30"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">finally</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-6-31"><a id="__codelineno-6-31" name="__codelineno-6-31" href="#__codelineno-6-31"></a><span class="w"> </span><span class="nx">setGeocoding</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span><span id="__span-6-32"><a id="__codelineno-6-32" name="__codelineno-6-32" href="#__codelineno-6-32"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-6-33"><a id="__codelineno-6-33" name="__codelineno-6-33" href="#__codelineno-6-33"></a><span class="p">};</span>
</span></code></pre></div>
<h3 id="location-service-create-backend">Location Service Create (Backend)<a class="headerlink" href="#location-service-create-backend" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a><span class="c1">// api/src/modules/map/locations/locations.service.ts</span>
</span><span id="__span-7-2"><a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a><span class="k">async</span><span class="w"> </span><span class="nx">create</span><span class="p">(</span><span class="nx">data</span><span class="o">:</span><span class="w"> </span><span class="kt">CreateLocationInput</span><span class="p">,</span><span class="w"> </span><span class="nx">userId</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-7-3"><a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a><span class="w"> </span><span class="c1">// Auto-geocode if address provided but no coordinates</span>
</span><span id="__span-7-4"><a id="__codelineno-7-4" name="__codelineno-7-4" href="#__codelineno-7-4"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">address</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">!</span><span class="nx">data</span><span class="p">.</span><span class="nx">latitude</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">!</span><span class="nx">data</span><span class="p">.</span><span class="nx">longitude</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-7-5"><a id="__codelineno-7-5" name="__codelineno-7-5" href="#__codelineno-7-5"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-7-6"><a id="__codelineno-7-6" name="__codelineno-7-6" href="#__codelineno-7-6"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">fullAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">postalCode</span>
</span><span id="__span-7-7"><a id="__codelineno-7-7" name="__codelineno-7-7" href="#__codelineno-7-7"></a><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="sb">`</span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">address</span><span class="si">}</span><span class="sb">, </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">postalCode</span><span class="si">}</span><span class="sb">`</span>
</span><span id="__span-7-8"><a id="__codelineno-7-8" name="__codelineno-7-8" href="#__codelineno-7-8"></a><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">address</span><span class="p">;</span>
</span><span id="__span-7-9"><a id="__codelineno-7-9" name="__codelineno-7-9" href="#__codelineno-7-9"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">geocodeResult</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">geocodingService</span><span class="p">.</span><span class="nx">geocode</span><span class="p">(</span><span class="nx">fullAddress</span><span class="p">);</span>
</span><span id="__span-7-10"><a id="__codelineno-7-10" name="__codelineno-7-10" href="#__codelineno-7-10"></a>
</span><span id="__span-7-11"><a id="__codelineno-7-11" name="__codelineno-7-11" href="#__codelineno-7-11"></a><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">latitude</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">geocodeResult</span><span class="p">.</span><span class="nx">latitude</span><span class="p">;</span>
</span><span id="__span-7-12"><a id="__codelineno-7-12" name="__codelineno-7-12" href="#__codelineno-7-12"></a><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">longitude</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">geocodeResult</span><span class="p">.</span><span class="nx">longitude</span><span class="p">;</span>
</span><span id="__span-7-13"><a id="__codelineno-7-13" name="__codelineno-7-13" href="#__codelineno-7-13"></a><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">geocodeConfidence</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">geocodeResult</span><span class="p">.</span><span class="nx">confidence</span><span class="p">;</span>
</span><span id="__span-7-14"><a id="__codelineno-7-14" name="__codelineno-7-14" href="#__codelineno-7-14"></a><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">geocodeProvider</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">geocodeResult</span><span class="p">.</span><span class="nx">provider</span><span class="p">;</span>
</span><span id="__span-7-15"><a id="__codelineno-7-15" name="__codelineno-7-15" href="#__codelineno-7-15"></a>
</span><span id="__span-7-16"><a id="__codelineno-7-16" name="__codelineno-7-16" href="#__codelineno-7-16"></a><span class="w"> </span><span class="nx">logger</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s1">&#39;Auto-geocoded location&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-7-17"><a id="__codelineno-7-17" name="__codelineno-7-17" href="#__codelineno-7-17"></a><span class="w"> </span><span class="nx">address</span><span class="o">:</span><span class="w"> </span><span class="kt">fullAddress</span><span class="p">,</span>
</span><span id="__span-7-18"><a id="__codelineno-7-18" name="__codelineno-7-18" href="#__codelineno-7-18"></a><span class="w"> </span><span class="nx">provider</span><span class="o">:</span><span class="w"> </span><span class="kt">geocodeResult.provider</span><span class="p">,</span>
</span><span id="__span-7-19"><a id="__codelineno-7-19" name="__codelineno-7-19" href="#__codelineno-7-19"></a><span class="w"> </span><span class="nx">confidence</span><span class="o">:</span><span class="w"> </span><span class="kt">geocodeResult.confidence</span><span class="p">,</span>
</span><span id="__span-7-20"><a id="__codelineno-7-20" name="__codelineno-7-20" href="#__codelineno-7-20"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-7-21"><a id="__codelineno-7-21" name="__codelineno-7-21" href="#__codelineno-7-21"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-7-22"><a id="__codelineno-7-22" name="__codelineno-7-22" href="#__codelineno-7-22"></a><span class="w"> </span><span class="nx">logger</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="s1">&#39;Auto-geocoding failed, creating location without coordinates&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">);</span>
</span><span id="__span-7-23"><a id="__codelineno-7-23" name="__codelineno-7-23" href="#__codelineno-7-23"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-7-24"><a id="__codelineno-7-24" name="__codelineno-7-24" href="#__codelineno-7-24"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-7-25"><a id="__codelineno-7-25" name="__codelineno-7-25" href="#__codelineno-7-25"></a>
</span><span id="__span-7-26"><a id="__codelineno-7-26" name="__codelineno-7-26" href="#__codelineno-7-26"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">prisma</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span>
</span><span id="__span-7-27"><a id="__codelineno-7-27" name="__codelineno-7-27" href="#__codelineno-7-27"></a><span class="w"> </span><span class="nx">data</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-7-28"><a id="__codelineno-7-28" name="__codelineno-7-28" href="#__codelineno-7-28"></a><span class="w"> </span><span class="nx">address</span><span class="o">:</span><span class="w"> </span><span class="kt">data.address</span><span class="p">,</span>
</span><span id="__span-7-29"><a id="__codelineno-7-29" name="__codelineno-7-29" href="#__codelineno-7-29"></a><span class="w"> </span><span class="nx">latitude</span><span class="o">:</span><span class="w"> </span><span class="kt">data.latitude</span><span class="p">,</span>
</span><span id="__span-7-30"><a id="__codelineno-7-30" name="__codelineno-7-30" href="#__codelineno-7-30"></a><span class="w"> </span><span class="nx">longitude</span><span class="o">:</span><span class="w"> </span><span class="kt">data.longitude</span><span class="p">,</span>
</span><span id="__span-7-31"><a id="__codelineno-7-31" name="__codelineno-7-31" href="#__codelineno-7-31"></a><span class="w"> </span><span class="nx">postalCode</span><span class="o">:</span><span class="w"> </span><span class="kt">data.postalCode</span><span class="p">,</span>
</span><span id="__span-7-32"><a id="__codelineno-7-32" name="__codelineno-7-32" href="#__codelineno-7-32"></a><span class="w"> </span><span class="nx">province</span><span class="o">:</span><span class="w"> </span><span class="kt">data.province</span><span class="p">,</span>
</span><span id="__span-7-33"><a id="__codelineno-7-33" name="__codelineno-7-33" href="#__codelineno-7-33"></a><span class="w"> </span><span class="nx">federalDistrict</span><span class="o">:</span><span class="w"> </span><span class="kt">data.federalDistrict</span><span class="p">,</span>
</span><span id="__span-7-34"><a id="__codelineno-7-34" name="__codelineno-7-34" href="#__codelineno-7-34"></a><span class="w"> </span><span class="nx">buildingType</span><span class="o">:</span><span class="w"> </span><span class="kt">data.buildingType</span><span class="p">,</span>
</span><span id="__span-7-35"><a id="__codelineno-7-35" name="__codelineno-7-35" href="#__codelineno-7-35"></a><span class="w"> </span><span class="nx">totalUnits</span><span class="o">:</span><span class="w"> </span><span class="kt">data.totalUnits</span><span class="p">,</span>
</span><span id="__span-7-36"><a id="__codelineno-7-36" name="__codelineno-7-36" href="#__codelineno-7-36"></a><span class="w"> </span><span class="nx">buildingNotes</span><span class="o">:</span><span class="w"> </span><span class="kt">data.buildingNotes</span><span class="p">,</span>
</span><span id="__span-7-37"><a id="__codelineno-7-37" name="__codelineno-7-37" href="#__codelineno-7-37"></a><span class="w"> </span><span class="nx">geocodeConfidence</span><span class="o">:</span><span class="w"> </span><span class="kt">data.geocodeConfidence</span><span class="p">,</span>
</span><span id="__span-7-38"><a id="__codelineno-7-38" name="__codelineno-7-38" href="#__codelineno-7-38"></a><span class="w"> </span><span class="nx">geocodeProvider</span><span class="o">:</span><span class="w"> </span><span class="kt">data.geocodeProvider</span><span class="p">,</span>
</span><span id="__span-7-39"><a id="__codelineno-7-39" name="__codelineno-7-39" href="#__codelineno-7-39"></a><span class="w"> </span><span class="nx">createdByUserId</span><span class="o">:</span><span class="w"> </span><span class="kt">userId</span><span class="p">,</span>
</span><span id="__span-7-40"><a id="__codelineno-7-40" name="__codelineno-7-40" href="#__codelineno-7-40"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-7-41"><a id="__codelineno-7-41" name="__codelineno-7-41" href="#__codelineno-7-41"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-7-42"><a id="__codelineno-7-42" name="__codelineno-7-42" href="#__codelineno-7-42"></a>
</span><span id="__span-7-43"><a id="__codelineno-7-43" name="__codelineno-7-43" href="#__codelineno-7-43"></a><span class="w"> </span><span class="c1">// Create history record</span>
</span><span id="__span-7-44"><a id="__codelineno-7-44" name="__codelineno-7-44" href="#__codelineno-7-44"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">prisma</span><span class="p">.</span><span class="nx">locationHistory</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span>
</span><span id="__span-7-45"><a id="__codelineno-7-45" name="__codelineno-7-45" href="#__codelineno-7-45"></a><span class="w"> </span><span class="nx">data</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-7-46"><a id="__codelineno-7-46" name="__codelineno-7-46" href="#__codelineno-7-46"></a><span class="w"> </span><span class="nx">locationId</span><span class="o">:</span><span class="w"> </span><span class="kt">location.id</span><span class="p">,</span>
</span><span id="__span-7-47"><a id="__codelineno-7-47" name="__codelineno-7-47" href="#__codelineno-7-47"></a><span class="w"> </span><span class="nx">action</span><span class="o">:</span><span class="w"> </span><span class="kt">LocationHistoryAction.CREATED</span><span class="p">,</span>
</span><span id="__span-7-48"><a id="__codelineno-7-48" name="__codelineno-7-48" href="#__codelineno-7-48"></a><span class="w"> </span><span class="nx">changedByUserId</span><span class="o">:</span><span class="w"> </span><span class="kt">userId</span><span class="p">,</span>
</span><span id="__span-7-49"><a id="__codelineno-7-49" name="__codelineno-7-49" href="#__codelineno-7-49"></a><span class="w"> </span><span class="nx">changes</span><span class="o">:</span><span class="w"> </span><span class="kt">JSON.stringify</span><span class="p">({</span><span class="w"> </span><span class="nx">created</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="w"> </span><span class="p">}),</span>
</span><span id="__span-7-50"><a id="__codelineno-7-50" name="__codelineno-7-50" href="#__codelineno-7-50"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-7-51"><a id="__codelineno-7-51" name="__codelineno-7-51" href="#__codelineno-7-51"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-7-52"><a id="__codelineno-7-52" name="__codelineno-7-52" href="#__codelineno-7-52"></a>
</span><span id="__span-7-53"><a id="__codelineno-7-53" name="__codelineno-7-53" href="#__codelineno-7-53"></a><span class="w"> </span><span class="nx">recordLocationQuery</span><span class="p">(</span><span class="s1">&#39;create&#39;</span><span class="p">);</span>
</span><span id="__span-7-54"><a id="__codelineno-7-54" name="__codelineno-7-54" href="#__codelineno-7-54"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">location</span><span class="p">;</span>
</span><span id="__span-7-55"><a id="__codelineno-7-55" name="__codelineno-7-55" href="#__codelineno-7-55"></a><span class="p">}</span>
</span></code></pre></div>
<h3 id="csv-import-detection-backend">CSV Import Detection (Backend)<a class="headerlink" href="#csv-import-detection-backend" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a><span class="c1">// api/src/modules/map/locations/locations.service.ts</span>
</span><span id="__span-8-2"><a id="__codelineno-8-2" name="__codelineno-8-2" href="#__codelineno-8-2"></a><span class="kd">function</span><span class="w"> </span><span class="nx">detectNarFormat</span><span class="p">(</span><span class="nx">headers</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">[])</span><span class="o">:</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-8-3"><a id="__codelineno-8-3" name="__codelineno-8-3" href="#__codelineno-8-3"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">normalizedHeaders</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">headers</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">h</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">h</span><span class="p">.</span><span class="nx">trim</span><span class="p">().</span><span class="nx">toUpperCase</span><span class="p">());</span>
</span><span id="__span-8-4"><a id="__codelineno-8-4" name="__codelineno-8-4" href="#__codelineno-8-4"></a><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">matchCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span>
</span><span id="__span-8-5"><a id="__codelineno-8-5" name="__codelineno-8-5" href="#__codelineno-8-5"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">matched</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nb">Set</span><span class="o">&lt;</span><span class="kt">string</span><span class="o">&gt;</span><span class="p">();</span>
</span><span id="__span-8-6"><a id="__codelineno-8-6" name="__codelineno-8-6" href="#__codelineno-8-6"></a>
</span><span id="__span-8-7"><a id="__codelineno-8-7" name="__codelineno-8-7" href="#__codelineno-8-7"></a><span class="w"> </span><span class="c1">// NAR columns to detect (need 3+ matches)</span>
</span><span id="__span-8-8"><a id="__codelineno-8-8" name="__codelineno-8-8" href="#__codelineno-8-8"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">NAR_DETECT_COLUMNS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
</span><span id="__span-8-9"><a id="__codelineno-8-9" name="__codelineno-8-9" href="#__codelineno-8-9"></a><span class="w"> </span><span class="s1">&#39;CIVIC_NO&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;OFFICIAL_STREET_NAME&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;OFFICIAL_STREET_TYPE&#39;</span><span class="p">,</span>
</span><span id="__span-8-10"><a id="__codelineno-8-10" name="__codelineno-8-10" href="#__codelineno-8-10"></a><span class="w"> </span><span class="s1">&#39;BG_X&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;BG_Y&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;MAIL_POSTAL_CODE&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;MAIL_PROV_ABVN&#39;</span><span class="p">,</span>
</span><span id="__span-8-11"><a id="__codelineno-8-11" name="__codelineno-8-11" href="#__codelineno-8-11"></a><span class="w"> </span><span class="s1">&#39;BG_LATITUDE&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;BG_LONGITUDE&#39;</span><span class="p">,</span>
</span><span id="__span-8-12"><a id="__codelineno-8-12" name="__codelineno-8-12" href="#__codelineno-8-12"></a><span class="w"> </span><span class="p">];</span>
</span><span id="__span-8-13"><a id="__codelineno-8-13" name="__codelineno-8-13" href="#__codelineno-8-13"></a>
</span><span id="__span-8-14"><a id="__codelineno-8-14" name="__codelineno-8-14" href="#__codelineno-8-14"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">col</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nx">NAR_DETECT_COLUMNS</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-8-15"><a id="__codelineno-8-15" name="__codelineno-8-15" href="#__codelineno-8-15"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">normalizedHeaders</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">col</span><span class="p">)</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">!</span><span class="nx">matched</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">col</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-8-16"><a id="__codelineno-8-16" name="__codelineno-8-16" href="#__codelineno-8-16"></a><span class="w"> </span><span class="nx">matched</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">col</span><span class="p">);</span>
</span><span id="__span-8-17"><a id="__codelineno-8-17" name="__codelineno-8-17" href="#__codelineno-8-17"></a><span class="w"> </span><span class="nx">matchCount</span><span class="o">++</span><span class="p">;</span>
</span><span id="__span-8-18"><a id="__codelineno-8-18" name="__codelineno-8-18" href="#__codelineno-8-18"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-8-19"><a id="__codelineno-8-19" name="__codelineno-8-19" href="#__codelineno-8-19"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-8-20"><a id="__codelineno-8-20" name="__codelineno-8-20" href="#__codelineno-8-20"></a>
</span><span id="__span-8-21"><a id="__codelineno-8-21" name="__codelineno-8-21" href="#__codelineno-8-21"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">matchCount</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mf">3</span><span class="p">;</span>
</span><span id="__span-8-22"><a id="__codelineno-8-22" name="__codelineno-8-22" href="#__codelineno-8-22"></a><span class="p">}</span>
</span></code></pre></div>
<h3 id="nar-lambert-coordinate-conversion-backend">NAR Lambert Coordinate Conversion (Backend)<a class="headerlink" href="#nar-lambert-coordinate-conversion-backend" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a><span class="c1">// api/src/modules/map/locations/locations.service.ts</span>
</span><span id="__span-9-2"><a id="__codelineno-9-2" name="__codelineno-9-2" href="#__codelineno-9-2"></a><span class="k">import</span><span class="w"> </span><span class="nx">proj4</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;proj4&#39;</span><span class="p">;</span>
</span><span id="__span-9-3"><a id="__codelineno-9-3" name="__codelineno-9-3" href="#__codelineno-9-3"></a>
</span><span id="__span-9-4"><a id="__codelineno-9-4" name="__codelineno-9-4" href="#__codelineno-9-4"></a><span class="c1">// Statistics Canada Lambert Conformal Conic (EPSG:3347) → WGS84 (EPSG:4326)</span>
</span><span id="__span-9-5"><a id="__codelineno-9-5" name="__codelineno-9-5" href="#__codelineno-9-5"></a><span class="nx">proj4</span><span class="p">.</span><span class="nx">defs</span><span class="p">(</span>
</span><span id="__span-9-6"><a id="__codelineno-9-6" name="__codelineno-9-6" href="#__codelineno-9-6"></a><span class="w"> </span><span class="s1">&#39;EPSG:3347&#39;</span><span class="p">,</span>
</span><span id="__span-9-7"><a id="__codelineno-9-7" name="__codelineno-9-7" href="#__codelineno-9-7"></a><span class="w"> </span><span class="s1">&#39;+proj=lcc +lat_1=49 +lat_2=77 +lat_0=63.390675 +lon_0=-91.86666666666666 &#39;</span><span class="w"> </span><span class="o">+</span>
</span><span id="__span-9-8"><a id="__codelineno-9-8" name="__codelineno-9-8" href="#__codelineno-9-8"></a><span class="w"> </span><span class="s1">&#39;+x_0=6200000 +y_0=3000000 +ellps=GRS80 +units=m +no_defs&#39;</span>
</span><span id="__span-9-9"><a id="__codelineno-9-9" name="__codelineno-9-9" href="#__codelineno-9-9"></a><span class="p">);</span>
</span><span id="__span-9-10"><a id="__codelineno-9-10" name="__codelineno-9-10" href="#__codelineno-9-10"></a>
</span><span id="__span-9-11"><a id="__codelineno-9-11" name="__codelineno-9-11" href="#__codelineno-9-11"></a><span class="cm">/** Convert BG_X/BG_Y (EPSG:3347 Lambert) to [lat, lng] (WGS84) */</span>
</span><span id="__span-9-12"><a id="__codelineno-9-12" name="__codelineno-9-12" href="#__codelineno-9-12"></a><span class="kd">function</span><span class="w"> </span><span class="nx">lambertToLatLng</span><span class="p">(</span><span class="nx">bgX</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">,</span><span class="w"> </span><span class="nx">bgY</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="kt">number</span><span class="p">,</span><span class="w"> </span><span class="kt">number</span><span class="p">]</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-9-13"><a id="__codelineno-9-13" name="__codelineno-9-13" href="#__codelineno-9-13"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">lng</span><span class="p">,</span><span class="w"> </span><span class="nx">lat</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">proj4</span><span class="p">(</span><span class="s1">&#39;EPSG:3347&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;EPSG:4326&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="nx">bgX</span><span class="p">,</span><span class="w"> </span><span class="nx">bgY</span><span class="p">]);</span>
</span><span id="__span-9-14"><a id="__codelineno-9-14" name="__codelineno-9-14" href="#__codelineno-9-14"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="nx">lat</span><span class="p">,</span><span class="w"> </span><span class="nx">lng</span><span class="p">];</span>
</span><span id="__span-9-15"><a id="__codelineno-9-15" name="__codelineno-9-15" href="#__codelineno-9-15"></a><span class="p">}</span>
</span><span id="__span-9-16"><a id="__codelineno-9-16" name="__codelineno-9-16" href="#__codelineno-9-16"></a>
</span><span id="__span-9-17"><a id="__codelineno-9-17" name="__codelineno-9-17" href="#__codelineno-9-17"></a><span class="c1">// Usage in NAR import</span>
</span><span id="__span-9-18"><a id="__codelineno-9-18" name="__codelineno-9-18" href="#__codelineno-9-18"></a><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">lat</span><span class="p">,</span><span class="w"> </span><span class="nx">lng</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">lambertToLatLng</span><span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_X</span><span class="p">,</span><span class="w"> </span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_Y</span><span class="p">);</span>
</span></code></pre></div>
<h3 id="spatial-filtering-by-cut-backend">Spatial Filtering by Cut (Backend)<a class="headerlink" href="#spatial-filtering-by-cut-backend" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="c1">// api/src/modules/map/locations/locations.service.ts</span>
</span><span id="__span-10-2"><a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a><span class="k">async</span><span class="w"> </span><span class="nx">findByBounds</span><span class="p">(</span><span class="nx">filters</span><span class="o">:</span><span class="w"> </span><span class="kt">BoundsQuery</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-3"><a id="__codelineno-10-3" name="__codelineno-10-3" href="#__codelineno-10-3"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">where</span><span class="o">:</span><span class="w"> </span><span class="kt">Prisma.LocationWhereInput</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-4"><a id="__codelineno-10-4" name="__codelineno-10-4" href="#__codelineno-10-4"></a><span class="w"> </span><span class="nx">latitude</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-5"><a id="__codelineno-10-5" name="__codelineno-10-5" href="#__codelineno-10-5"></a><span class="w"> </span><span class="nx">gte</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nx">Prisma</span><span class="p">.</span><span class="nx">Decimal</span><span class="p">(</span><span class="nx">filters</span><span class="p">.</span><span class="nx">minLat</span><span class="p">),</span>
</span><span id="__span-10-6"><a id="__codelineno-10-6" name="__codelineno-10-6" href="#__codelineno-10-6"></a><span class="w"> </span><span class="nx">lte</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nx">Prisma</span><span class="p">.</span><span class="nx">Decimal</span><span class="p">(</span><span class="nx">filters</span><span class="p">.</span><span class="nx">maxLat</span><span class="p">),</span>
</span><span id="__span-10-7"><a id="__codelineno-10-7" name="__codelineno-10-7" href="#__codelineno-10-7"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-10-8"><a id="__codelineno-10-8" name="__codelineno-10-8" href="#__codelineno-10-8"></a><span class="w"> </span><span class="nx">longitude</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-9"><a id="__codelineno-10-9" name="__codelineno-10-9" href="#__codelineno-10-9"></a><span class="w"> </span><span class="nx">gte</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nx">Prisma</span><span class="p">.</span><span class="nx">Decimal</span><span class="p">(</span><span class="nx">filters</span><span class="p">.</span><span class="nx">minLng</span><span class="p">),</span>
</span><span id="__span-10-10"><a id="__codelineno-10-10" name="__codelineno-10-10" href="#__codelineno-10-10"></a><span class="w"> </span><span class="nx">lte</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nx">Prisma</span><span class="p">.</span><span class="nx">Decimal</span><span class="p">(</span><span class="nx">filters</span><span class="p">.</span><span class="nx">maxLng</span><span class="p">),</span>
</span><span id="__span-10-11"><a id="__codelineno-10-11" name="__codelineno-10-11" href="#__codelineno-10-11"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-10-12"><a id="__codelineno-10-12" name="__codelineno-10-12" href="#__codelineno-10-12"></a><span class="w"> </span><span class="p">};</span>
</span><span id="__span-10-13"><a id="__codelineno-10-13" name="__codelineno-10-13" href="#__codelineno-10-13"></a>
</span><span id="__span-10-14"><a id="__codelineno-10-14" name="__codelineno-10-14" href="#__codelineno-10-14"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">locations</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">prisma</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">findMany</span><span class="p">({</span>
</span><span id="__span-10-15"><a id="__codelineno-10-15" name="__codelineno-10-15" href="#__codelineno-10-15"></a><span class="w"> </span><span class="nx">where</span><span class="p">,</span>
</span><span id="__span-10-16"><a id="__codelineno-10-16" name="__codelineno-10-16" href="#__codelineno-10-16"></a><span class="w"> </span><span class="nx">select</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-17"><a id="__codelineno-10-17" name="__codelineno-10-17" href="#__codelineno-10-17"></a><span class="w"> </span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span>
</span><span id="__span-10-18"><a id="__codelineno-10-18" name="__codelineno-10-18" href="#__codelineno-10-18"></a><span class="w"> </span><span class="nx">latitude</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span>
</span><span id="__span-10-19"><a id="__codelineno-10-19" name="__codelineno-10-19" href="#__codelineno-10-19"></a><span class="w"> </span><span class="nx">longitude</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span>
</span><span id="__span-10-20"><a id="__codelineno-10-20" name="__codelineno-10-20" href="#__codelineno-10-20"></a><span class="w"> </span><span class="nx">address</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span>
</span><span id="__span-10-21"><a id="__codelineno-10-21" name="__codelineno-10-21" href="#__codelineno-10-21"></a><span class="w"> </span><span class="nx">addresses</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-22"><a id="__codelineno-10-22" name="__codelineno-10-22" href="#__codelineno-10-22"></a><span class="w"> </span><span class="nx">select</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-23"><a id="__codelineno-10-23" name="__codelineno-10-23" href="#__codelineno-10-23"></a><span class="w"> </span><span class="nx">supportLevel</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span>
</span><span id="__span-10-24"><a id="__codelineno-10-24" name="__codelineno-10-24" href="#__codelineno-10-24"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-10-25"><a id="__codelineno-10-25" name="__codelineno-10-25" href="#__codelineno-10-25"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-10-26"><a id="__codelineno-10-26" name="__codelineno-10-26" href="#__codelineno-10-26"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-10-27"><a id="__codelineno-10-27" name="__codelineno-10-27" href="#__codelineno-10-27"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-10-28"><a id="__codelineno-10-28" name="__codelineno-10-28" href="#__codelineno-10-28"></a>
</span><span id="__span-10-29"><a id="__codelineno-10-29" name="__codelineno-10-29" href="#__codelineno-10-29"></a><span class="w"> </span><span class="c1">// If cut filter provided, apply point-in-polygon</span>
</span><span id="__span-10-30"><a id="__codelineno-10-30" name="__codelineno-10-30" href="#__codelineno-10-30"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">filters</span><span class="p">.</span><span class="nx">cutId</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-31"><a id="__codelineno-10-31" name="__codelineno-10-31" href="#__codelineno-10-31"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">cut</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">prisma</span><span class="p">.</span><span class="nx">cut</span><span class="p">.</span><span class="nx">findUnique</span><span class="p">({</span>
</span><span id="__span-10-32"><a id="__codelineno-10-32" name="__codelineno-10-32" href="#__codelineno-10-32"></a><span class="w"> </span><span class="nx">where</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="kt">filters.cutId</span><span class="w"> </span><span class="p">},</span>
</span><span id="__span-10-33"><a id="__codelineno-10-33" name="__codelineno-10-33" href="#__codelineno-10-33"></a><span class="w"> </span><span class="nx">select</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">geojson</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="w"> </span><span class="p">},</span>
</span><span id="__span-10-34"><a id="__codelineno-10-34" name="__codelineno-10-34" href="#__codelineno-10-34"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-10-35"><a id="__codelineno-10-35" name="__codelineno-10-35" href="#__codelineno-10-35"></a>
</span><span id="__span-10-36"><a id="__codelineno-10-36" name="__codelineno-10-36" href="#__codelineno-10-36"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">cut</span><span class="o">?</span><span class="p">.</span><span class="nx">geojson</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-37"><a id="__codelineno-10-37" name="__codelineno-10-37" href="#__codelineno-10-37"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">polygons</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">parseGeoJsonPolygon</span><span class="p">(</span><span class="nx">cut</span><span class="p">.</span><span class="nx">geojson</span><span class="p">);</span>
</span><span id="__span-10-38"><a id="__codelineno-10-38" name="__codelineno-10-38" href="#__codelineno-10-38"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">locations</span><span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">loc</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-10-39"><a id="__codelineno-10-39" name="__codelineno-10-39" href="#__codelineno-10-39"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">lat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Number</span><span class="p">(</span><span class="nx">loc</span><span class="p">.</span><span class="nx">latitude</span><span class="p">);</span>
</span><span id="__span-10-40"><a id="__codelineno-10-40" name="__codelineno-10-40" href="#__codelineno-10-40"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">lng</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Number</span><span class="p">(</span><span class="nx">loc</span><span class="p">.</span><span class="nx">longitude</span><span class="p">);</span>
</span><span id="__span-10-41"><a id="__codelineno-10-41" name="__codelineno-10-41" href="#__codelineno-10-41"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">polygons</span><span class="p">.</span><span class="nx">some</span><span class="p">((</span><span class="nx">poly</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">isPointInPolygon</span><span class="p">(</span><span class="nx">lat</span><span class="p">,</span><span class="w"> </span><span class="nx">lng</span><span class="p">,</span><span class="w"> </span><span class="nx">poly</span><span class="p">));</span>
</span><span id="__span-10-42"><a id="__codelineno-10-42" name="__codelineno-10-42" href="#__codelineno-10-42"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-10-43"><a id="__codelineno-10-43" name="__codelineno-10-43" href="#__codelineno-10-43"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-10-44"><a id="__codelineno-10-44" name="__codelineno-10-44" href="#__codelineno-10-44"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-10-45"><a id="__codelineno-10-45" name="__codelineno-10-45" href="#__codelineno-10-45"></a>
</span><span id="__span-10-46"><a id="__codelineno-10-46" name="__codelineno-10-46" href="#__codelineno-10-46"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">locations</span><span class="p">;</span>
</span><span id="__span-10-47"><a id="__codelineno-10-47" name="__codelineno-10-47" href="#__codelineno-10-47"></a><span class="p">}</span>
</span></code></pre></div>
<h2 id="troubleshooting">Troubleshooting<a class="headerlink" href="#troubleshooting" title="Permanent link">&para;</a></h2>
<h3 id="issue-geocoding-fails-for-valid-address">Issue: Geocoding Fails for Valid Address<a class="headerlink" href="#issue-geocoding-fails-for-valid-address" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>"Geocoding failed" error message</li>
<li>Location created without coordinates</li>
<li>Low geocode confidence score (&lt;50)</li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>Invalid API key for geocoding provider</li>
<li>Provider quota exceeded</li>
<li>Address format not recognized by provider</li>
<li>Provider service down</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Check API keys</strong>:</li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="c1"># Verify API keys are set in .env</span>
</span><span id="__span-11-2"><a id="__codelineno-11-2" name="__codelineno-11-2" href="#__codelineno-11-2"></a>grep<span class="w"> </span><span class="s2">&quot;GOOGLE_MAPS_API_KEY\|MAPBOX_ACCESS_TOKEN\|LOCATIONIQ_API_KEY&quot;</span><span class="w"> </span>.env
</span></code></pre></div>
<ol>
<li><strong>Test geocoding endpoint directly</strong>:</li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a>curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>http://localhost:4000/api/map/locations/geocode<span class="w"> </span><span class="se">\</span>
</span><span id="__span-12-2"><a id="__codelineno-12-2" name="__codelineno-12-2" href="#__codelineno-12-2"></a><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer YOUR_TOKEN&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-12-3"><a id="__codelineno-12-3" name="__codelineno-12-3" href="#__codelineno-12-3"></a><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-12-4"><a id="__codelineno-12-4" name="__codelineno-12-4" href="#__codelineno-12-4"></a><span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;address&quot;:&quot;123 Main Street, Ottawa, ON K1A 0B1&quot;}&#39;</span>
</span></code></pre></div>
<ol>
<li><strong>Check provider order in env</strong>:</li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-13-1"><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a><span class="c1"># Try different provider order</span>
</span><span id="__span-13-2"><a id="__codelineno-13-2" name="__codelineno-13-2" href="#__codelineno-13-2"></a><span class="nv">GEOCODING_PROVIDERS</span><span class="o">=</span>GOOGLE,NOMINATIM,PHOTON,MAPBOX,LOCATIONIQ,ARCGIS
</span></code></pre></div>
<ol>
<li><strong>View API logs</strong>:</li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-14-1"><a id="__codelineno-14-1" name="__codelineno-14-1" href="#__codelineno-14-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>logs<span class="w"> </span>-f<span class="w"> </span>api<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>geocode
</span></code></pre></div>
<h3 id="issue-nar-import-fails-or-hangs">Issue: NAR Import Fails or Hangs<a class="headerlink" href="#issue-nar-import-fails-or-hangs" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>NAR import progress stuck at 0%</li>
<li>Import fails with "File not found" error</li>
<li>Import fails with "Invalid coordinates" error</li>
<li>Memory errors during large imports</li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>NAR files not in <code>/data</code> directory</li>
<li>Multi-part files missing (e.g., Address_35_part_2.csv)</li>
<li>Incorrect province code</li>
<li>Invalid BG_X/BG_Y coordinates</li>
<li>Cut polygon filter too complex</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Verify NAR files exist</strong>:</li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-15-1"><a id="__codelineno-15-1" name="__codelineno-15-1" href="#__codelineno-15-1"></a><span class="c1"># Check /data directory in container</span>
</span><span id="__span-15-2"><a id="__codelineno-15-2" name="__codelineno-15-2" href="#__codelineno-15-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>api<span class="w"> </span>ls<span class="w"> </span>-lh<span class="w"> </span>/data
</span><span id="__span-15-3"><a id="__codelineno-15-3" name="__codelineno-15-3" href="#__codelineno-15-3"></a>
</span><span id="__span-15-4"><a id="__codelineno-15-4" name="__codelineno-15-4" href="#__codelineno-15-4"></a><span class="c1"># Verify file naming matches NAR format</span>
</span><span id="__span-15-5"><a id="__codelineno-15-5" name="__codelineno-15-5" href="#__codelineno-15-5"></a><span class="c1"># Address_{PROV_CODE}_part_{N}.csv</span>
</span><span id="__span-15-6"><a id="__codelineno-15-6" name="__codelineno-15-6" href="#__codelineno-15-6"></a><span class="c1"># Location_{PROV_CODE}.csv</span>
</span></code></pre></div>
<ol>
<li><strong>Check province code mapping</strong>:</li>
</ol>
<div class="language-text highlight"><pre><span></span><code><span id="__span-16-1"><a id="__codelineno-16-1" name="__codelineno-16-1" href="#__codelineno-16-1"></a>10 = Newfoundland and Labrador
</span><span id="__span-16-2"><a id="__codelineno-16-2" name="__codelineno-16-2" href="#__codelineno-16-2"></a>24 = Quebec
</span><span id="__span-16-3"><a id="__codelineno-16-3" name="__codelineno-16-3" href="#__codelineno-16-3"></a>35 = Ontario
</span><span id="__span-16-4"><a id="__codelineno-16-4" name="__codelineno-16-4" href="#__codelineno-16-4"></a>48 = Alberta
</span><span id="__span-16-5"><a id="__codelineno-16-5" name="__codelineno-16-5" href="#__codelineno-16-5"></a>59 = British Columbia
</span><span id="__span-16-6"><a id="__codelineno-16-6" name="__codelineno-16-6" href="#__codelineno-16-6"></a>62 = Nunavut
</span></code></pre></div>
<ol>
<li><strong>Test coordinate conversion</strong>:</li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-17-1"><a id="__codelineno-17-1" name="__codelineno-17-1" href="#__codelineno-17-1"></a><span class="c1"># Verify proj4 is installed</span>
</span><span id="__span-17-2"><a id="__codelineno-17-2" name="__codelineno-17-2" href="#__codelineno-17-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>api<span class="w"> </span>node<span class="w"> </span>-e<span class="w"> </span><span class="s2">&quot;const proj4 = require(&#39;proj4&#39;); console.log(proj4.version);&quot;</span>
</span></code></pre></div>
<ol>
<li><strong>Monitor import progress</strong>:</li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-18-1"><a id="__codelineno-18-1" name="__codelineno-18-1" href="#__codelineno-18-1"></a><span class="c1"># Watch API logs during import</span>
</span><span id="__span-18-2"><a id="__codelineno-18-2" name="__codelineno-18-2" href="#__codelineno-18-2"></a>docker<span class="w"> </span>compose<span class="w"> </span>logs<span class="w"> </span>-f<span class="w"> </span>api<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s2">&quot;NAR import&quot;</span>
</span><span id="__span-18-3"><a id="__codelineno-18-3" name="__codelineno-18-3" href="#__codelineno-18-3"></a>
</span><span id="__span-18-4"><a id="__codelineno-18-4" name="__codelineno-18-4" href="#__codelineno-18-4"></a><span class="c1"># Check Redis for progress key</span>
</span><span id="__span-18-5"><a id="__codelineno-18-5" name="__codelineno-18-5" href="#__codelineno-18-5"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>redis<span class="w"> </span>redis-cli<span class="w"> </span>GET<span class="w"> </span><span class="s2">&quot;NAR_IMPORT_PROGRESS&quot;</span>
</span></code></pre></div>
<ol>
<li>
<p><strong>Use smaller filters for testing</strong>:</p>
</li>
<li>
<p>Start with single postal code prefix (e.g., "K1A")</p>
</li>
<li>Use small cut polygon</li>
<li>Enable residential-only filter (reduces records by ~50%)</li>
</ol>
<h3 id="issue-duplicate-locations-created-on-import">Issue: Duplicate Locations Created on Import<a class="headerlink" href="#issue-duplicate-locations-created-on-import" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Same address appears multiple times in table</li>
<li>Export CSV has duplicate rows</li>
<li>Location count doesn't match expected NAR count</li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>Re-importing same CSV file without checking for duplicates</li>
<li>NAR Address multi-part files have overlapping records</li>
<li>Different LOC_GUID for same physical address (NAR data issue)</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Use NAR GUID fields for deduplication</strong>:</li>
</ol>
<p>The system deduplicates by <code>narLocGuid</code> and <code>narAddrGuid</code>:</p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-19-1"><a id="__codelineno-19-1" name="__codelineno-19-1" href="#__codelineno-19-1"></a><span class="c1">// Check for existing location before creating</span>
</span><span id="__span-19-2"><a id="__codelineno-19-2" name="__codelineno-19-2" href="#__codelineno-19-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">existing</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">prisma</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">findFirst</span><span class="p">({</span>
</span><span id="__span-19-3"><a id="__codelineno-19-3" name="__codelineno-19-3" href="#__codelineno-19-3"></a><span class="w"> </span><span class="nx">where</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">narLocGuid</span><span class="o">:</span><span class="w"> </span><span class="kt">row.LOC_GUID</span><span class="w"> </span><span class="p">},</span>
</span><span id="__span-19-4"><a id="__codelineno-19-4" name="__codelineno-19-4" href="#__codelineno-19-4"></a><span class="p">});</span>
</span><span id="__span-19-5"><a id="__codelineno-19-5" name="__codelineno-19-5" href="#__codelineno-19-5"></a>
</span><span id="__span-19-6"><a id="__codelineno-19-6" name="__codelineno-19-6" href="#__codelineno-19-6"></a><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">existing</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-19-7"><a id="__codelineno-19-7" name="__codelineno-19-7" href="#__codelineno-19-7"></a><span class="w"> </span><span class="nx">skipped</span><span class="o">++</span><span class="p">;</span>
</span><span id="__span-19-8"><a id="__codelineno-19-8" name="__codelineno-19-8" href="#__codelineno-19-8"></a><span class="w"> </span><span class="k">continue</span><span class="p">;</span>
</span><span id="__span-19-9"><a id="__codelineno-19-9" name="__codelineno-19-9" href="#__codelineno-19-9"></a><span class="p">}</span>
</span></code></pre></div>
<ol>
<li><strong>Delete duplicates manually</strong>:</li>
</ol>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-20-1"><a id="__codelineno-20-1" name="__codelineno-20-1" href="#__codelineno-20-1"></a><span class="c1">-- Find duplicate locations by address</span>
</span><span id="__span-20-2"><a id="__codelineno-20-2" name="__codelineno-20-2" href="#__codelineno-20-2"></a><span class="k">SELECT</span><span class="w"> </span><span class="n">address</span><span class="p">,</span><span class="w"> </span><span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="k">count</span>
</span><span id="__span-20-3"><a id="__codelineno-20-3" name="__codelineno-20-3" href="#__codelineno-20-3"></a><span class="k">FROM</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span>
</span><span id="__span-20-4"><a id="__codelineno-20-4" name="__codelineno-20-4" href="#__codelineno-20-4"></a><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">address</span>
</span><span id="__span-20-5"><a id="__codelineno-20-5" name="__codelineno-20-5" href="#__codelineno-20-5"></a><span class="k">HAVING</span><span class="w"> </span><span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span>
</span><span id="__span-20-6"><a id="__codelineno-20-6" name="__codelineno-20-6" href="#__codelineno-20-6"></a>
</span><span id="__span-20-7"><a id="__codelineno-20-7" name="__codelineno-20-7" href="#__codelineno-20-7"></a><span class="c1">-- Keep first, delete rest</span>
</span><span id="__span-20-8"><a id="__codelineno-20-8" name="__codelineno-20-8" href="#__codelineno-20-8"></a><span class="k">DELETE</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span>
</span><span id="__span-20-9"><a id="__codelineno-20-9" name="__codelineno-20-9" href="#__codelineno-20-9"></a><span class="k">WHERE</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">IN</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-20-10"><a id="__codelineno-20-10" name="__codelineno-20-10" href="#__codelineno-20-10"></a><span class="w"> </span><span class="k">SELECT</span><span class="w"> </span><span class="k">MIN</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
</span><span id="__span-20-11"><a id="__codelineno-20-11" name="__codelineno-20-11" href="#__codelineno-20-11"></a><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span>
</span><span id="__span-20-12"><a id="__codelineno-20-12" name="__codelineno-20-12" href="#__codelineno-20-12"></a><span class="w"> </span><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">address</span>
</span><span id="__span-20-13"><a id="__codelineno-20-13" name="__codelineno-20-13" href="#__codelineno-20-13"></a><span class="p">);</span>
</span></code></pre></div>
<ol>
<li><strong>Use server-side NAR import</strong> (better deduplication):</li>
</ol>
<p>Server-side import joins Address + Location files on LOC_GUID before inserting, preventing duplicates.</p>
<h3 id="issue-low-geocode-confidence-for-nar-data">Issue: Low Geocode Confidence for NAR Data<a class="headerlink" href="#issue-low-geocode-confidence-for-nar-data" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>NAR locations have geocodeConfidence &lt; 70</li>
<li>Locations appear in wrong place on map</li>
<li>"Low confidence" warnings in admin</li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>BG_X/BG_Y coordinates missing in NAR Location file</li>
<li>BG_LATITUDE/BG_LONGITUDE used instead of converted Lambert coords</li>
<li>proj4 conversion error</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Verify coordinate source</strong>:</li>
</ol>
<p>NAR Location files have TWO coordinate fields:</p>
<ul>
<li><code>BG_LATITUDE</code> / <code>BG_LONGITUDE</code>: Direct WGS84 (use these if available)</li>
<li>
<p><code>BG_X</code> / <code>BG_Y</code>: Lambert Conformal Conic EPSG:3347 (requires conversion)</p>
</li>
<li>
<p><strong>Use BG_LATITUDE/BG_LONGITUDE if available</strong>:</p>
</li>
</ul>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-21-1"><a id="__codelineno-21-1" name="__codelineno-21-1" href="#__codelineno-21-1"></a><span class="c1">// Priority: use direct WGS84 coords if available</span>
</span><span id="__span-21-2"><a id="__codelineno-21-2" name="__codelineno-21-2" href="#__codelineno-21-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">lat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_LATITUDE</span>
</span><span id="__span-21-3"><a id="__codelineno-21-3" name="__codelineno-21-3" href="#__codelineno-21-3"></a><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="nb">parseFloat</span><span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_LATITUDE</span><span class="p">)</span>
</span><span id="__span-21-4"><a id="__codelineno-21-4" name="__codelineno-21-4" href="#__codelineno-21-4"></a><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_X</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_Y</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="nx">lambertToLatLng</span><span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_X</span><span class="p">,</span><span class="w"> </span><span class="nx">row</span><span class="p">.</span><span class="nx">BG_Y</span><span class="p">)[</span><span class="mf">0</span><span class="p">]</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kc">null</span><span class="p">);</span>
</span></code></pre></div>
<ol>
<li><strong>Re-geocode low-confidence locations</strong>:</li>
</ol>
<p>Use bulk re-geocoding feature with confidence filter &lt;70.</p>
<h2 id="performance-considerations">Performance Considerations<a class="headerlink" href="#performance-considerations" title="Permanent link">&para;</a></h2>
<h3 id="query-optimization">Query Optimization<a class="headerlink" href="#query-optimization" title="Permanent link">&para;</a></h3>
<p><strong>Bounding Box Queries:</strong></p>
<p>Always use indexed lat/lng queries for map bounds:</p>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-22-1"><a id="__codelineno-22-1" name="__codelineno-22-1" href="#__codelineno-22-1"></a><span class="c1">-- Efficient: uses idx_locations_lat_lng index</span>
</span><span id="__span-22-2"><a id="__codelineno-22-2" name="__codelineno-22-2" href="#__codelineno-22-2"></a><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span>
</span><span id="__span-22-3"><a id="__codelineno-22-3" name="__codelineno-22-3" href="#__codelineno-22-3"></a><span class="k">WHERE</span><span class="w"> </span><span class="n">latitude</span><span class="w"> </span><span class="k">BETWEEN</span><span class="w"> </span><span class="mi">45</span><span class="p">.</span><span class="mi">0</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="mi">46</span><span class="p">.</span><span class="mi">0</span>
</span><span id="__span-22-4"><a id="__codelineno-22-4" name="__codelineno-22-4" href="#__codelineno-22-4"></a><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">longitude</span><span class="w"> </span><span class="k">BETWEEN</span><span class="w"> </span><span class="o">-</span><span class="mi">76</span><span class="p">.</span><span class="mi">0</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="o">-</span><span class="mi">75</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span>
</span><span id="__span-22-5"><a id="__codelineno-22-5" name="__codelineno-22-5" href="#__codelineno-22-5"></a>
</span><span id="__span-22-6"><a id="__codelineno-22-6" name="__codelineno-22-6" href="#__codelineno-22-6"></a><span class="c1">-- Inefficient: no index</span>
</span><span id="__span-22-7"><a id="__codelineno-22-7" name="__codelineno-22-7" href="#__codelineno-22-7"></a><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="ss">&quot;Location&quot;</span>
</span><span id="__span-22-8"><a id="__codelineno-22-8" name="__codelineno-22-8" href="#__codelineno-22-8"></a><span class="k">WHERE</span><span class="w"> </span><span class="n">ST_Contains</span><span class="p">(</span><span class="n">polygon</span><span class="p">,</span><span class="w"> </span><span class="n">point</span><span class="p">);</span><span class="w"> </span><span class="c1">-- PostGIS not used</span>
</span></code></pre></div>
<p><strong>Point-in-Polygon:</strong></p>
<p>For small result sets (&lt;1000 locations), use application-level ray-casting:</p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-23-1"><a id="__codelineno-23-1" name="__codelineno-23-1" href="#__codelineno-23-1"></a><span class="c1">// api/src/utils/spatial.ts</span>
</span><span id="__span-23-2"><a id="__codelineno-23-2" name="__codelineno-23-2" href="#__codelineno-23-2"></a><span class="k">export</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">isPointInPolygon</span><span class="p">(</span>
</span><span id="__span-23-3"><a id="__codelineno-23-3" name="__codelineno-23-3" href="#__codelineno-23-3"></a><span class="w"> </span><span class="nx">lat</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">,</span>
</span><span id="__span-23-4"><a id="__codelineno-23-4" name="__codelineno-23-4" href="#__codelineno-23-4"></a><span class="w"> </span><span class="nx">lng</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">,</span>
</span><span id="__span-23-5"><a id="__codelineno-23-5" name="__codelineno-23-5" href="#__codelineno-23-5"></a><span class="w"> </span><span class="nx">polygonCoords</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">[][]</span>
</span><span id="__span-23-6"><a id="__codelineno-23-6" name="__codelineno-23-6" href="#__codelineno-23-6"></a><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-23-7"><a id="__codelineno-23-7" name="__codelineno-23-7" href="#__codelineno-23-7"></a><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">inside</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span>
</span><span id="__span-23-8"><a id="__codelineno-23-8" name="__codelineno-23-8" href="#__codelineno-23-8"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">,</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">polygonCoords</span><span class="p">.</span><span class="nx">length</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="nx">polygonCoords</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-23-9"><a id="__codelineno-23-9" name="__codelineno-23-9" href="#__codelineno-23-9"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">xi</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">polygonCoords</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="o">!</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="o">!</span><span class="p">;</span><span class="w"> </span><span class="c1">// lat</span>
</span><span id="__span-23-10"><a id="__codelineno-23-10" name="__codelineno-23-10" href="#__codelineno-23-10"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">yi</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">polygonCoords</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="o">!</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span><span class="o">!</span><span class="p">;</span><span class="w"> </span><span class="c1">// lng</span>
</span><span id="__span-23-11"><a id="__codelineno-23-11" name="__codelineno-23-11" href="#__codelineno-23-11"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">xj</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">polygonCoords</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span><span class="o">!</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="o">!</span><span class="p">;</span>
</span><span id="__span-23-12"><a id="__codelineno-23-12" name="__codelineno-23-12" href="#__codelineno-23-12"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">yj</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">polygonCoords</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span><span class="o">!</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span><span class="o">!</span><span class="p">;</span>
</span><span id="__span-23-13"><a id="__codelineno-23-13" name="__codelineno-23-13" href="#__codelineno-23-13"></a>
</span><span id="__span-23-14"><a id="__codelineno-23-14" name="__codelineno-23-14" href="#__codelineno-23-14"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">intersect</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">((</span><span class="nx">yi</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nx">lng</span><span class="p">)</span><span class="w"> </span><span class="o">!==</span><span class="w"> </span><span class="p">(</span><span class="nx">yj</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nx">lng</span><span class="p">))</span><span class="w"> </span><span class="o">&amp;&amp;</span>
</span><span id="__span-23-15"><a id="__codelineno-23-15" name="__codelineno-23-15" href="#__codelineno-23-15"></a><span class="w"> </span><span class="p">(</span><span class="nx">lat</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="p">(</span><span class="nx">xj</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">xi</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">(</span><span class="nx">lng</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">yi</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="p">(</span><span class="nx">yj</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">yi</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">xi</span><span class="p">);</span>
</span><span id="__span-23-16"><a id="__codelineno-23-16" name="__codelineno-23-16" href="#__codelineno-23-16"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">intersect</span><span class="p">)</span><span class="w"> </span><span class="nx">inside</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="nx">inside</span><span class="p">;</span>
</span><span id="__span-23-17"><a id="__codelineno-23-17" name="__codelineno-23-17" href="#__codelineno-23-17"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-23-18"><a id="__codelineno-23-18" name="__codelineno-23-18" href="#__codelineno-23-18"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">inside</span><span class="p">;</span>
</span><span id="__span-23-19"><a id="__codelineno-23-19" name="__codelineno-23-19" href="#__codelineno-23-19"></a><span class="p">}</span>
</span></code></pre></div>
<p>For large result sets (&gt;10,000 locations), consider PostGIS extension.</p>
<h3 id="geocoding-rate-limits">Geocoding Rate Limits<a class="headerlink" href="#geocoding-rate-limits" title="Permanent link">&para;</a></h3>
<p><strong>Provider Limits:</strong></p>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Free Tier</th>
<th>Rate Limit</th>
</tr>
</thead>
<tbody>
<tr>
<td>Google</td>
<td>$200/month credit</td>
<td>50 req/sec</td>
</tr>
<tr>
<td>Mapbox</td>
<td>100,000/month</td>
<td>600 req/min</td>
</tr>
<tr>
<td>Nominatim</td>
<td>Unlimited</td>
<td>1 req/sec</td>
</tr>
<tr>
<td>Photon</td>
<td>Unlimited</td>
<td>No limit (self-hosted recommended)</td>
</tr>
<tr>
<td>LocationIQ</td>
<td>5,000/day</td>
<td>2 req/sec</td>
</tr>
<tr>
<td>ArcGIS</td>
<td>20,000/month</td>
<td>50 req/sec</td>
</tr>
</tbody>
</table>
<p><strong>Best Practices:</strong></p>
<ol>
<li><strong>Enable Redis caching</strong> (default: 7 days TTL)</li>
<li><strong>Use bulk geocoding jobs</strong> (BullMQ queue with rate limiting)</li>
<li><strong>Prefer NAR imports</strong> (coordinates included, no geocoding needed)</li>
<li><strong>Batch geocoding requests</strong> (50 locations per batch)</li>
</ol>
<h3 id="nar-import-performance">NAR Import Performance<a class="headerlink" href="#nar-import-performance" title="Permanent link">&para;</a></h3>
<p><strong>Large File Streaming:</strong></p>
<p>NAR Address files can be 10+ GB. Use server-side streaming to avoid memory issues:</p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-24-1"><a id="__codelineno-24-1" name="__codelineno-24-1" href="#__codelineno-24-1"></a><span class="c1">// api/src/modules/map/locations/nar-import.service.ts</span>
</span><span id="__span-24-2"><a id="__codelineno-24-2" name="__codelineno-24-2" href="#__codelineno-24-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">createReadStream</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;fs&#39;</span><span class="p">;</span>
</span><span id="__span-24-3"><a id="__codelineno-24-3" name="__codelineno-24-3" href="#__codelineno-24-3"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">parse</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;csv-parse&#39;</span><span class="p">;</span>
</span><span id="__span-24-4"><a id="__codelineno-24-4" name="__codelineno-24-4" href="#__codelineno-24-4"></a>
</span><span id="__span-24-5"><a id="__codelineno-24-5" name="__codelineno-24-5" href="#__codelineno-24-5"></a><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">streamNarFile</span><span class="p">(</span><span class="nx">filePath</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-24-6"><a id="__codelineno-24-6" name="__codelineno-24-6" href="#__codelineno-24-6"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span><span class="w"> </span><span class="nx">reject</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-24-7"><a id="__codelineno-24-7" name="__codelineno-24-7" href="#__codelineno-24-7"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">stream</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">createReadStream</span><span class="p">(</span><span class="nx">filePath</span><span class="p">)</span>
</span><span id="__span-24-8"><a id="__codelineno-24-8" name="__codelineno-24-8" href="#__codelineno-24-8"></a><span class="w"> </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">parse</span><span class="p">({</span><span class="w"> </span><span class="nx">columns</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span><span class="w"> </span><span class="nx">skip_empty_lines</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="w"> </span><span class="p">}));</span>
</span><span id="__span-24-9"><a id="__codelineno-24-9" name="__codelineno-24-9" href="#__codelineno-24-9"></a>
</span><span id="__span-24-10"><a id="__codelineno-24-10" name="__codelineno-24-10" href="#__codelineno-24-10"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">batch</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span>
</span><span id="__span-24-11"><a id="__codelineno-24-11" name="__codelineno-24-11" href="#__codelineno-24-11"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">BATCH_SIZE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">500</span><span class="p">;</span>
</span><span id="__span-24-12"><a id="__codelineno-24-12" name="__codelineno-24-12" href="#__codelineno-24-12"></a>
</span><span id="__span-24-13"><a id="__codelineno-24-13" name="__codelineno-24-13" href="#__codelineno-24-13"></a><span class="w"> </span><span class="nx">stream</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;data&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">(</span><span class="nx">row</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-24-14"><a id="__codelineno-24-14" name="__codelineno-24-14" href="#__codelineno-24-14"></a><span class="w"> </span><span class="nx">batch</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">row</span><span class="p">);</span>
</span><span id="__span-24-15"><a id="__codelineno-24-15" name="__codelineno-24-15" href="#__codelineno-24-15"></a>
</span><span id="__span-24-16"><a id="__codelineno-24-16" name="__codelineno-24-16" href="#__codelineno-24-16"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">batch</span><span class="p">.</span><span class="nx">length</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="nx">BATCH_SIZE</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-24-17"><a id="__codelineno-24-17" name="__codelineno-24-17" href="#__codelineno-24-17"></a><span class="w"> </span><span class="nx">stream</span><span class="p">.</span><span class="nx">pause</span><span class="p">();</span><span class="w"> </span><span class="c1">// Backpressure</span>
</span><span id="__span-24-18"><a id="__codelineno-24-18" name="__codelineno-24-18" href="#__codelineno-24-18"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">insertBatch</span><span class="p">(</span><span class="nx">batch</span><span class="p">);</span>
</span><span id="__span-24-19"><a id="__codelineno-24-19" name="__codelineno-24-19" href="#__codelineno-24-19"></a><span class="w"> </span><span class="nx">batch</span><span class="p">.</span><span class="nx">length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span>
</span><span id="__span-24-20"><a id="__codelineno-24-20" name="__codelineno-24-20" href="#__codelineno-24-20"></a><span class="w"> </span><span class="nx">stream</span><span class="p">.</span><span class="nx">resume</span><span class="p">();</span>
</span><span id="__span-24-21"><a id="__codelineno-24-21" name="__codelineno-24-21" href="#__codelineno-24-21"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-24-22"><a id="__codelineno-24-22" name="__codelineno-24-22" href="#__codelineno-24-22"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-24-23"><a id="__codelineno-24-23" name="__codelineno-24-23" href="#__codelineno-24-23"></a>
</span><span id="__span-24-24"><a id="__codelineno-24-24" name="__codelineno-24-24" href="#__codelineno-24-24"></a><span class="w"> </span><span class="nx">stream</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;end&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-24-25"><a id="__codelineno-24-25" name="__codelineno-24-25" href="#__codelineno-24-25"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">batch</span><span class="p">.</span><span class="nx">length</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">insertBatch</span><span class="p">(</span><span class="nx">batch</span><span class="p">);</span>
</span><span id="__span-24-26"><a id="__codelineno-24-26" name="__codelineno-24-26" href="#__codelineno-24-26"></a><span class="w"> </span><span class="nx">resolve</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span><span id="__span-24-27"><a id="__codelineno-24-27" name="__codelineno-24-27" href="#__codelineno-24-27"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-24-28"><a id="__codelineno-24-28" name="__codelineno-24-28" href="#__codelineno-24-28"></a>
</span><span id="__span-24-29"><a id="__codelineno-24-29" name="__codelineno-24-29" href="#__codelineno-24-29"></a><span class="w"> </span><span class="nx">stream</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;error&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">reject</span><span class="p">);</span>
</span><span id="__span-24-30"><a id="__codelineno-24-30" name="__codelineno-24-30" href="#__codelineno-24-30"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-24-31"><a id="__codelineno-24-31" name="__codelineno-24-31" href="#__codelineno-24-31"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Transaction Batching:</strong></p>
<p>Insert locations in transaction batches to improve performance:</p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-25-1"><a id="__codelineno-25-1" name="__codelineno-25-1" href="#__codelineno-25-1"></a><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">insertBatch</span><span class="p">(</span><span class="nx">rows</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">[])</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-2"><a id="__codelineno-25-2" name="__codelineno-25-2" href="#__codelineno-25-2"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">prisma</span><span class="p">.</span><span class="nx">$transaction</span><span class="p">(</span>
</span><span id="__span-25-3"><a id="__codelineno-25-3" name="__codelineno-25-3" href="#__codelineno-25-3"></a><span class="w"> </span><span class="nx">rows</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">row</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span>
</span><span id="__span-25-4"><a id="__codelineno-25-4" name="__codelineno-25-4" href="#__codelineno-25-4"></a><span class="w"> </span><span class="nx">prisma</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span>
</span><span id="__span-25-5"><a id="__codelineno-25-5" name="__codelineno-25-5" href="#__codelineno-25-5"></a><span class="w"> </span><span class="nx">data</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-6"><a id="__codelineno-25-6" name="__codelineno-25-6" href="#__codelineno-25-6"></a><span class="w"> </span><span class="nx">address</span><span class="o">:</span><span class="w"> </span><span class="kt">row.address</span><span class="p">,</span>
</span><span id="__span-25-7"><a id="__codelineno-25-7" name="__codelineno-25-7" href="#__codelineno-25-7"></a><span class="w"> </span><span class="nx">latitude</span><span class="o">:</span><span class="w"> </span><span class="kt">row.latitude</span><span class="p">,</span>
</span><span id="__span-25-8"><a id="__codelineno-25-8" name="__codelineno-25-8" href="#__codelineno-25-8"></a><span class="w"> </span><span class="nx">longitude</span><span class="o">:</span><span class="w"> </span><span class="kt">row.longitude</span><span class="p">,</span>
</span><span id="__span-25-9"><a id="__codelineno-25-9" name="__codelineno-25-9" href="#__codelineno-25-9"></a><span class="w"> </span><span class="c1">// ... other fields</span>
</span><span id="__span-25-10"><a id="__codelineno-25-10" name="__codelineno-25-10" href="#__codelineno-25-10"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-25-11"><a id="__codelineno-25-11" name="__codelineno-25-11" href="#__codelineno-25-11"></a><span class="w"> </span><span class="p">})</span>
</span><span id="__span-25-12"><a id="__codelineno-25-12" name="__codelineno-25-12" href="#__codelineno-25-12"></a><span class="w"> </span><span class="p">),</span>
</span><span id="__span-25-13"><a id="__codelineno-25-13" name="__codelineno-25-13" href="#__codelineno-25-13"></a><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">timeout</span><span class="o">:</span><span class="w"> </span><span class="kt">30000</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c1">// 30s timeout for large batches</span>
</span><span id="__span-25-14"><a id="__codelineno-25-14" name="__codelineno-25-14" href="#__codelineno-25-14"></a><span class="w"> </span><span class="p">);</span>
</span><span id="__span-25-15"><a id="__codelineno-25-15" name="__codelineno-25-15" href="#__codelineno-25-15"></a><span class="p">}</span>
</span></code></pre></div>
<h3 id="map-rendering-performance">Map Rendering Performance<a class="headerlink" href="#map-rendering-performance" title="Permanent link">&para;</a></h3>
<p><strong>Marker Clustering:</strong></p>
<p>For maps with &gt;1000 locations, use marker clustering to improve render performance:</p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-26-1"><a id="__codelineno-26-1" name="__codelineno-26-1" href="#__codelineno-26-1"></a><span class="c1">// admin/src/components/map/AdminMapView.tsx</span>
</span><span id="__span-26-2"><a id="__codelineno-26-2" name="__codelineno-26-2" href="#__codelineno-26-2"></a><span class="k">import</span><span class="w"> </span><span class="nx">MarkerClusterGroup</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;react-leaflet-cluster&#39;</span><span class="p">;</span>
</span><span id="__span-26-3"><a id="__codelineno-26-3" name="__codelineno-26-3" href="#__codelineno-26-3"></a>
</span><span id="__span-26-4"><a id="__codelineno-26-4" name="__codelineno-26-4" href="#__codelineno-26-4"></a><span class="o">&lt;</span><span class="nx">MarkerClusterGroup</span><span class="o">&gt;</span>
</span><span id="__span-26-5"><a id="__codelineno-26-5" name="__codelineno-26-5" href="#__codelineno-26-5"></a><span class="w"> </span><span class="p">{</span><span class="nx">locations</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">loc</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-26-6"><a id="__codelineno-26-6" name="__codelineno-26-6" href="#__codelineno-26-6"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">CircleMarker</span>
</span><span id="__span-26-7"><a id="__codelineno-26-7" name="__codelineno-26-7" href="#__codelineno-26-7"></a><span class="w"> </span><span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">loc</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span>
</span><span id="__span-26-8"><a id="__codelineno-26-8" name="__codelineno-26-8" href="#__codelineno-26-8"></a><span class="w"> </span><span class="nx">center</span><span class="o">=</span><span class="p">{[</span><span class="nx">loc</span><span class="p">.</span><span class="nx">latitude</span><span class="p">,</span><span class="w"> </span><span class="nx">loc</span><span class="p">.</span><span class="nx">longitude</span><span class="p">]}</span>
</span><span id="__span-26-9"><a id="__codelineno-26-9" name="__codelineno-26-9" href="#__codelineno-26-9"></a><span class="w"> </span><span class="nx">radius</span><span class="o">=</span><span class="p">{</span><span class="mf">8</span><span class="p">}</span>
</span><span id="__span-26-10"><a id="__codelineno-26-10" name="__codelineno-26-10" href="#__codelineno-26-10"></a><span class="w"> </span><span class="nx">pathOptions</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">color</span><span class="o">:</span><span class="w"> </span><span class="kt">getSupportLevelColor</span><span class="p">(</span><span class="nx">loc</span><span class="p">.</span><span class="nx">supportLevel</span><span class="p">)</span><span class="w"> </span><span class="p">}}</span>
</span><span id="__span-26-11"><a id="__codelineno-26-11" name="__codelineno-26-11" href="#__codelineno-26-11"></a><span class="w"> </span><span class="o">/&gt;</span>
</span><span id="__span-26-12"><a id="__codelineno-26-12" name="__codelineno-26-12" href="#__codelineno-26-12"></a><span class="w"> </span><span class="p">))}</span>
</span><span id="__span-26-13"><a id="__codelineno-26-13" name="__codelineno-26-13" href="#__codelineno-26-13"></a><span class="o">&lt;</span><span class="err">/MarkerClusterGroup&gt;</span>
</span></code></pre></div>
<p><strong>Viewport Filtering:</strong></p>
<p>Only load locations within map bounds + buffer:</p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-27-1"><a id="__codelineno-27-1" name="__codelineno-27-1" href="#__codelineno-27-1"></a><span class="c1">// admin/src/pages/public/MapPage.tsx</span>
</span><span id="__span-27-2"><a id="__codelineno-27-2" name="__codelineno-27-2" href="#__codelineno-27-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">handleMapMove</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">useCallback</span><span class="p">(</span>
</span><span id="__span-27-3"><a id="__codelineno-27-3" name="__codelineno-27-3" href="#__codelineno-27-3"></a><span class="w"> </span><span class="nx">debounce</span><span class="p">(()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-27-4"><a id="__codelineno-27-4" name="__codelineno-27-4" href="#__codelineno-27-4"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">mapRef</span><span class="p">.</span><span class="nx">current</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="p">;</span>
</span><span id="__span-27-5"><a id="__codelineno-27-5" name="__codelineno-27-5" href="#__codelineno-27-5"></a>
</span><span id="__span-27-6"><a id="__codelineno-27-6" name="__codelineno-27-6" href="#__codelineno-27-6"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">bounds</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mapRef</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">getBounds</span><span class="p">();</span>
</span><span id="__span-27-7"><a id="__codelineno-27-7" name="__codelineno-27-7" href="#__codelineno-27-7"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">buffer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.1</span><span class="p">;</span><span class="w"> </span><span class="c1">// 10% buffer</span>
</span><span id="__span-27-8"><a id="__codelineno-27-8" name="__codelineno-27-8" href="#__codelineno-27-8"></a>
</span><span id="__span-27-9"><a id="__codelineno-27-9" name="__codelineno-27-9" href="#__codelineno-27-9"></a><span class="w"> </span><span class="nx">fetchLocations</span><span class="p">({</span>
</span><span id="__span-27-10"><a id="__codelineno-27-10" name="__codelineno-27-10" href="#__codelineno-27-10"></a><span class="w"> </span><span class="nx">minLat</span><span class="o">:</span><span class="w"> </span><span class="kt">bounds.getSouth</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">buffer</span><span class="p">,</span>
</span><span id="__span-27-11"><a id="__codelineno-27-11" name="__codelineno-27-11" href="#__codelineno-27-11"></a><span class="w"> </span><span class="nx">maxLat</span><span class="o">:</span><span class="w"> </span><span class="kt">bounds.getNorth</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">buffer</span><span class="p">,</span>
</span><span id="__span-27-12"><a id="__codelineno-27-12" name="__codelineno-27-12" href="#__codelineno-27-12"></a><span class="w"> </span><span class="nx">minLng</span><span class="o">:</span><span class="w"> </span><span class="kt">bounds.getWest</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">buffer</span><span class="p">,</span>
</span><span id="__span-27-13"><a id="__codelineno-27-13" name="__codelineno-27-13" href="#__codelineno-27-13"></a><span class="w"> </span><span class="nx">maxLng</span><span class="o">:</span><span class="w"> </span><span class="kt">bounds.getEast</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">buffer</span><span class="p">,</span>
</span><span id="__span-27-14"><a id="__codelineno-27-14" name="__codelineno-27-14" href="#__codelineno-27-14"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-27-15"><a id="__codelineno-27-15" name="__codelineno-27-15" href="#__codelineno-27-15"></a><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="mf">500</span><span class="p">),</span>
</span><span id="__span-27-16"><a id="__codelineno-27-16" name="__codelineno-27-16" href="#__codelineno-27-16"></a><span class="w"> </span><span class="p">[]</span>
</span><span id="__span-27-17"><a id="__codelineno-27-17" name="__codelineno-27-17" href="#__codelineno-27-17"></a><span class="p">);</span>
</span></code></pre></div>
<h2 id="related-documentation">Related Documentation<a class="headerlink" href="#related-documentation" title="Permanent link">&para;</a></h2>
<p><strong>Backend Modules:</strong></p>
<ul>
<li><a href="../../backend/modules/map/locations.md">Locations Backend Module</a> — API implementation</li>
<li><a href="../../backend/modules/map/geocoding.md">Geocoding Service</a> — Multi-provider geocoding</li>
<li><a href="../../backend/modules/map/spatial.md">Spatial Utils</a> — Point-in-polygon algorithms</li>
</ul>
<p><strong>Frontend Pages:</strong></p>
<ul>
<li><a href="../../../frontend/pages/admin/locations-page/">LocationsPage</a> — Admin CRUD interface</li>
<li><a href="../../frontend/pages/admin/map-view.md">AdminMapView</a> — Interactive map component</li>
<li><a href="../../../frontend/pages/public/map-page/">Public MapPage</a> — Public map view</li>
</ul>
<p><strong>Database:</strong></p>
<ul>
<li><a href="../../../database/models/map/">Map Models</a> — Location, Address, Cut schemas</li>
<li><a href="../../../database/models/map/#locationhistory-model">Location History</a> — Audit trail</li>
<li><a href="../../database/queries.md#spatial-queries">Spatial Queries</a> — Optimization tips</li>
</ul>
<p><strong>Features:</strong></p>
<ul>
<li><a href="../geocoding/">Geocoding</a> — Multi-provider geocoding system</li>
<li><a href="../cuts/">Cuts</a> — Geographic polygon overlays</li>
<li><a href="../canvassing/">Canvassing</a> — Field organizing workflow</li>
<li><a href="../nar-import/">NAR Import</a> — Canadian electoral data import</li>
<li><a href="../data-quality/">Data Quality Dashboard</a> — Geocoding quality metrics</li>
</ul>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../" class="md-footer__link md-footer__link--prev" aria-label="Previous: Map Module">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
Map Module
</div>
</div>
</a>
<a href="../geocoding/" class="md-footer__link md-footer__link--next" aria-label="Next: Geocoding">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Geocoding
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 2024 The Bunker Operations <a href="#__consent">Change cookie settings</a>
</div>
</div>
<div class="md-social">
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://listmonk.bnkops.com/subscription/form" target="_blank" rel="noopener" title="Newsletter" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M536.4-26.3c9.8-3.5 20.6-1 28 6.3s9.8 18.2 6.3 28l-178 496.9c-5 13.9-18.1 23.1-32.8 23.1-14.2 0-27-8.6-32.3-21.7l-64.2-158c-4.5-11-2.5-23.6 5.2-32.6l94.5-112.4c5.1-6.1 4.7-15-.9-20.6s-14.6-6-20.6-.9l-112.4 94.3c-9.1 7.6-21.6 9.6-32.6 5.2L38.1 216.8c-13.1-5.3-21.7-18.1-21.7-32.3 0-14.7 9.2-27.8 23.1-32.8z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../../../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.expand", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.sections", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../../../assets/javascripts/bundle.79ae519e.min.js"></script>
<script src="../../../../javascripts/home.js"></script>
<script src="../../../../javascripts/github-widget.js"></script>
<script src="../../../../javascripts/gitea-widget.js"></script>
<script src="../../../../assets/js/env-config.js"></script>
<script src="../../../../assets/js/video-player.js"></script>
</body>
</html>