Diego Ripley
2025-08-10 10:02:41 -04:00
parent bc9e7b5f8c
commit f5a2831cf6
13 changed files with 1882 additions and 773 deletions
File diff suppressed because one or more lines are too long
@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Statistics Canada Tables Stats</title>
<link rel="stylesheet" href="./src/styles.css">
</head>
<body>
<div id="grid-container" class="grid-container"></div>
<script type="module">
async function init() {
try {
} catch (error) {
console.error('Failed to map app:', error);
}
}
// Initialize when DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
{
"name": "census-map-visualization",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^7.1.1"
},
"dependencies": {
"maplibre-gl": "^5.6.2",
"simple-statistics": "^7.8.3"
}
}
+815
View File
@@ -0,0 +1,815 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
maplibre-gl:
specifier: ^5.6.2
version: 5.6.2
simple-statistics:
specifier: ^7.8.3
version: 7.8.8
devDependencies:
vite:
specifier: ^7.1.1
version: 7.1.1
packages:
'@esbuild/aix-ppc64@0.25.8':
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.25.8':
resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.25.8':
resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.25.8':
resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.25.8':
resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.25.8':
resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.25.8':
resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.25.8':
resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.25.8':
resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.25.8':
resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.25.8':
resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.25.8':
resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.25.8':
resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.25.8':
resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.25.8':
resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.25.8':
resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.25.8':
resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.8':
resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.8':
resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.25.8':
resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.8':
resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.25.8':
resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.25.8':
resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.25.8':
resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.25.8':
resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.25.8':
resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@mapbox/geojson-rewind@0.5.2':
resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==}
hasBin: true
'@mapbox/jsonlint-lines-primitives@2.0.2':
resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==}
engines: {node: '>= 0.6'}
'@mapbox/point-geometry@1.1.0':
resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==}
'@mapbox/tiny-sdf@2.0.7':
resolution: {integrity: sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==}
'@mapbox/unitbezier@0.0.1':
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
'@mapbox/vector-tile@2.0.4':
resolution: {integrity: sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==}
'@mapbox/whoots-js@3.1.0':
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
engines: {node: '>=6.0.0'}
'@maplibre/maplibre-gl-style-spec@23.3.0':
resolution: {integrity: sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==}
hasBin: true
'@maplibre/vt-pbf@4.0.3':
resolution: {integrity: sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==}
'@rollup/rollup-android-arm-eabi@4.46.2':
resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.46.2':
resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.46.2':
resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.46.2':
resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.46.2':
resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.46.2':
resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.46.2':
resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.46.2':
resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.46.2':
resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.46.2':
resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-loongarch64-gnu@4.46.2':
resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
cpu: [loong64]
os: [linux]
'@rollup/rollup-linux-ppc64-gnu@4.46.2':
resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.46.2':
resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-riscv64-musl@4.46.2':
resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.46.2':
resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.46.2':
resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.46.2':
resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.46.2':
resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.46.2':
resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.46.2':
resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==}
cpu: [x64]
os: [win32]
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/geojson-vt@3.2.5':
resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==}
'@types/geojson@7946.0.16':
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
'@types/supercluster@7.1.3':
resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==}
earcut@3.0.2:
resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==}
esbuild@0.25.8:
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
engines: {node: '>=18'}
hasBin: true
fdir@6.4.6:
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
geojson-vt@4.0.2:
resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==}
get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
gl-matrix@3.4.4:
resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==}
json-stringify-pretty-compact@4.0.0:
resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==}
kdbush@4.0.2:
resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==}
maplibre-gl@5.6.2:
resolution: {integrity: sha512-SEqYThhUCFf6Lm0TckpgpKnto5u4JsdPYdFJb6g12VtuaFsm3nYdBO+fOmnUYddc8dXihgoGnuXvPPooUcRv5w==}
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
murmurhash-js@1.0.0:
resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
pbf@4.0.1:
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
hasBin: true
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.3:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
potpack@2.1.0:
resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==}
protocol-buffers-schema@3.6.0:
resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==}
quickselect@3.0.0:
resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==}
resolve-protobuf-schema@2.1.0:
resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==}
rollup@4.46.2:
resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
rw@1.3.3:
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
simple-statistics@7.8.8:
resolution: {integrity: sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
supercluster@8.0.1:
resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==}
tinyglobby@0.2.14:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
tinyqueue@3.0.0:
resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==}
vite@7.1.1:
resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
jiti: '>=1.21.0'
less: ^4.0.0
lightningcss: ^1.21.0
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
snapshots:
'@esbuild/aix-ppc64@0.25.8':
optional: true
'@esbuild/android-arm64@0.25.8':
optional: true
'@esbuild/android-arm@0.25.8':
optional: true
'@esbuild/android-x64@0.25.8':
optional: true
'@esbuild/darwin-arm64@0.25.8':
optional: true
'@esbuild/darwin-x64@0.25.8':
optional: true
'@esbuild/freebsd-arm64@0.25.8':
optional: true
'@esbuild/freebsd-x64@0.25.8':
optional: true
'@esbuild/linux-arm64@0.25.8':
optional: true
'@esbuild/linux-arm@0.25.8':
optional: true
'@esbuild/linux-ia32@0.25.8':
optional: true
'@esbuild/linux-loong64@0.25.8':
optional: true
'@esbuild/linux-mips64el@0.25.8':
optional: true
'@esbuild/linux-ppc64@0.25.8':
optional: true
'@esbuild/linux-riscv64@0.25.8':
optional: true
'@esbuild/linux-s390x@0.25.8':
optional: true
'@esbuild/linux-x64@0.25.8':
optional: true
'@esbuild/netbsd-arm64@0.25.8':
optional: true
'@esbuild/netbsd-x64@0.25.8':
optional: true
'@esbuild/openbsd-arm64@0.25.8':
optional: true
'@esbuild/openbsd-x64@0.25.8':
optional: true
'@esbuild/openharmony-arm64@0.25.8':
optional: true
'@esbuild/sunos-x64@0.25.8':
optional: true
'@esbuild/win32-arm64@0.25.8':
optional: true
'@esbuild/win32-ia32@0.25.8':
optional: true
'@esbuild/win32-x64@0.25.8':
optional: true
'@mapbox/geojson-rewind@0.5.2':
dependencies:
get-stream: 6.0.1
minimist: 1.2.8
'@mapbox/jsonlint-lines-primitives@2.0.2': {}
'@mapbox/point-geometry@1.1.0': {}
'@mapbox/tiny-sdf@2.0.7': {}
'@mapbox/unitbezier@0.0.1': {}
'@mapbox/vector-tile@2.0.4':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@types/geojson': 7946.0.16
pbf: 4.0.1
'@mapbox/whoots-js@3.1.0': {}
'@maplibre/maplibre-gl-style-spec@23.3.0':
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/unitbezier': 0.0.1
json-stringify-pretty-compact: 4.0.0
minimist: 1.2.8
quickselect: 3.0.0
rw: 1.3.3
tinyqueue: 3.0.0
'@maplibre/vt-pbf@4.0.3':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@mapbox/vector-tile': 2.0.4
'@types/geojson-vt': 3.2.5
'@types/supercluster': 7.1.3
geojson-vt: 4.0.2
pbf: 4.0.1
supercluster: 8.0.1
'@rollup/rollup-android-arm-eabi@4.46.2':
optional: true
'@rollup/rollup-android-arm64@4.46.2':
optional: true
'@rollup/rollup-darwin-arm64@4.46.2':
optional: true
'@rollup/rollup-darwin-x64@4.46.2':
optional: true
'@rollup/rollup-freebsd-arm64@4.46.2':
optional: true
'@rollup/rollup-freebsd-x64@4.46.2':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.46.2':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.46.2':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.46.2':
optional: true
'@rollup/rollup-linux-arm64-musl@4.46.2':
optional: true
'@rollup/rollup-linux-loongarch64-gnu@4.46.2':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.46.2':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.46.2':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.46.2':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.46.2':
optional: true
'@rollup/rollup-linux-x64-gnu@4.46.2':
optional: true
'@rollup/rollup-linux-x64-musl@4.46.2':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.46.2':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.46.2':
optional: true
'@rollup/rollup-win32-x64-msvc@4.46.2':
optional: true
'@types/estree@1.0.8': {}
'@types/geojson-vt@3.2.5':
dependencies:
'@types/geojson': 7946.0.16
'@types/geojson@7946.0.16': {}
'@types/supercluster@7.1.3':
dependencies:
'@types/geojson': 7946.0.16
earcut@3.0.2: {}
esbuild@0.25.8:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.8
'@esbuild/android-arm': 0.25.8
'@esbuild/android-arm64': 0.25.8
'@esbuild/android-x64': 0.25.8
'@esbuild/darwin-arm64': 0.25.8
'@esbuild/darwin-x64': 0.25.8
'@esbuild/freebsd-arm64': 0.25.8
'@esbuild/freebsd-x64': 0.25.8
'@esbuild/linux-arm': 0.25.8
'@esbuild/linux-arm64': 0.25.8
'@esbuild/linux-ia32': 0.25.8
'@esbuild/linux-loong64': 0.25.8
'@esbuild/linux-mips64el': 0.25.8
'@esbuild/linux-ppc64': 0.25.8
'@esbuild/linux-riscv64': 0.25.8
'@esbuild/linux-s390x': 0.25.8
'@esbuild/linux-x64': 0.25.8
'@esbuild/netbsd-arm64': 0.25.8
'@esbuild/netbsd-x64': 0.25.8
'@esbuild/openbsd-arm64': 0.25.8
'@esbuild/openbsd-x64': 0.25.8
'@esbuild/openharmony-arm64': 0.25.8
'@esbuild/sunos-x64': 0.25.8
'@esbuild/win32-arm64': 0.25.8
'@esbuild/win32-ia32': 0.25.8
'@esbuild/win32-x64': 0.25.8
fdir@6.4.6(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
fsevents@2.3.3:
optional: true
geojson-vt@4.0.2: {}
get-stream@6.0.1: {}
gl-matrix@3.4.4: {}
json-stringify-pretty-compact@4.0.0: {}
kdbush@4.0.2: {}
maplibre-gl@5.6.2:
dependencies:
'@mapbox/geojson-rewind': 0.5.2
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/point-geometry': 1.1.0
'@mapbox/tiny-sdf': 2.0.7
'@mapbox/unitbezier': 0.0.1
'@mapbox/vector-tile': 2.0.4
'@mapbox/whoots-js': 3.1.0
'@maplibre/maplibre-gl-style-spec': 23.3.0
'@maplibre/vt-pbf': 4.0.3
'@types/geojson': 7946.0.16
'@types/geojson-vt': 3.2.5
'@types/supercluster': 7.1.3
earcut: 3.0.2
geojson-vt: 4.0.2
gl-matrix: 3.4.4
kdbush: 4.0.2
murmurhash-js: 1.0.0
pbf: 4.0.1
potpack: 2.1.0
quickselect: 3.0.0
supercluster: 8.0.1
tinyqueue: 3.0.0
minimist@1.2.8: {}
murmurhash-js@1.0.0: {}
nanoid@3.3.11: {}
pbf@4.0.1:
dependencies:
resolve-protobuf-schema: 2.1.0
picocolors@1.1.1: {}
picomatch@4.0.3: {}
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
potpack@2.1.0: {}
protocol-buffers-schema@3.6.0: {}
quickselect@3.0.0: {}
resolve-protobuf-schema@2.1.0:
dependencies:
protocol-buffers-schema: 3.6.0
rollup@4.46.2:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.46.2
'@rollup/rollup-android-arm64': 4.46.2
'@rollup/rollup-darwin-arm64': 4.46.2
'@rollup/rollup-darwin-x64': 4.46.2
'@rollup/rollup-freebsd-arm64': 4.46.2
'@rollup/rollup-freebsd-x64': 4.46.2
'@rollup/rollup-linux-arm-gnueabihf': 4.46.2
'@rollup/rollup-linux-arm-musleabihf': 4.46.2
'@rollup/rollup-linux-arm64-gnu': 4.46.2
'@rollup/rollup-linux-arm64-musl': 4.46.2
'@rollup/rollup-linux-loongarch64-gnu': 4.46.2
'@rollup/rollup-linux-ppc64-gnu': 4.46.2
'@rollup/rollup-linux-riscv64-gnu': 4.46.2
'@rollup/rollup-linux-riscv64-musl': 4.46.2
'@rollup/rollup-linux-s390x-gnu': 4.46.2
'@rollup/rollup-linux-x64-gnu': 4.46.2
'@rollup/rollup-linux-x64-musl': 4.46.2
'@rollup/rollup-win32-arm64-msvc': 4.46.2
'@rollup/rollup-win32-ia32-msvc': 4.46.2
'@rollup/rollup-win32-x64-msvc': 4.46.2
fsevents: 2.3.3
rw@1.3.3: {}
simple-statistics@7.8.8: {}
source-map-js@1.2.1: {}
supercluster@8.0.1:
dependencies:
kdbush: 4.0.2
tinyglobby@0.2.14:
dependencies:
fdir: 6.4.6(picomatch@4.0.3)
picomatch: 4.0.3
tinyqueue@3.0.0: {}
vite@7.1.1:
dependencies:
esbuild: 0.25.8
fdir: 6.4.6(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.46.2
tinyglobby: 0.2.14
optionalDependencies:
fsevents: 2.3.3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,452 @@
import maplibregl from 'maplibre-gl';
import * as ss from 'simple-statistics';
import PeliasGeocoder from './pelias-geocoder';
// 2021 Census of Population characteristics
import availableFields from './fields'
let currentField = 'total_1';
let currentClassification = null;
let mapLoaded = false;
let hoveredFeatureId = null;
// Get field from URL parameters
function getFieldFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const fieldParam = urlParams.get('field');
if (fieldParam && availableFields.includes(fieldParam)) {
return fieldParam;
}
return 'total_1'; // default
}
// Update URL with current field
function updateURL(field) {
const url = new URL(window.location);
url.searchParams.set('field', field);
window.history.replaceState({}, '', url);
}
// Initialize field from URL
currentField = getFieldFromURL();
document.getElementById('currentField').textContent = `Current field: ${currentField}`;
const map = new maplibregl.Map({
container: 'map',
style: "https://tiles.openfreemap.org/styles/liberty",
zoom: 10,
center: [-75.695000, 45.424721],
hash: true,
maxZoom: 18,
attributionControl: false,
dragRotate: false,
keyboard: false,
pitchWithRotate: false
});
map.on('style.load', () => {
map.setProjection({
type: 'globe',
});
});
map.on('load', () => {
mapLoaded = true;
map.addSource('my-vector-tiles', {
type: 'vector',
tiles: ['https://tiles.diegoripley.ca/files/census_of_population_vector_tiles_subset_august_12_2025/da_2021_cop/{z}/{x}/{y}.mvt'],
minzoom: 8,
maxzoom: 14,
promoteId: 'da_dguid' // Promote DGUID to feature id for hover state
});
// Add controls
map.addControl(new maplibregl.FullscreenControl(), 'top-left');
// Add Pelias Geocoder
const geocoder = new PeliasGeocoder({
params: {
'boundary.country': 'CAN',
'boundary.rect.min_lat': 40,
'boundary.rect.max_lat': 60,
'boundary.rect.min_lon': -140,
'boundary.rect.max_lon': -50
},
flyTo: {
duration: 100,
curve: 1.5
},
marker: {
icon: 'marker',
color: '#FF0000'
},
placeholder: 'Search for places...'
});
map.addControl(geocoder, 'top-left');
// Disable rotation
map.touchZoomRotate.disableRotation();
// Add main visualization layer
map.addLayer({
'id': 'my-layer',
'type': 'fill',
'source': 'my-vector-tiles',
'source-layer': 'da_2021_cop',
'paint': {
'fill-color': '#cccccc',
'fill-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
0,
0.7
]
}
});
// Add hover outline layer
map.addLayer({
'id': 'my-layer-hover',
'type': 'line',
'source': 'my-vector-tiles',
'source-layer': 'da_2021_cop',
'paint': {
'line-color': '#ff0000',
'line-width': [
'case',
['boolean', ['feature-state', 'hover'], false],
3,
0
],
'line-dasharray': [2, 2],
'line-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
1,
0
]
},
'layout': {
'line-cap': 'round',
'line-join': 'round'
}
});
// Add static outline layer (always visible)
map.addLayer({
'id': 'my-layer-outline',
'type': 'line',
'source': 'my-vector-tiles',
'source-layer': 'da_2021_cop',
'paint': {
'line-color': '#000',
'line-width': 0.5,
'line-opacity': 0.5
}
});
// Use CKMeans by default
const actualFieldName = `count_${currentField}`;
addVisualizationLayerWithCKMeans(actualFieldName);
// Set up hover effects
setupHoverEffects();
});
function setupHoverEffects() {
// Mouse move handler for hover state
map.on('mousemove', 'my-layer', (e) => {
if (e.features.length > 0) {
// Remove previous hover state
if (hoveredFeatureId !== null) {
map.setFeatureState(
{ source: 'my-vector-tiles', sourceLayer: 'da_2021_cop', id: hoveredFeatureId },
{ hover: false }
);
}
// Set new hover state
hoveredFeatureId = e.features[0].properties.da_dguid;
if (hoveredFeatureId) {
map.setFeatureState(
{ source: 'my-vector-tiles', sourceLayer: 'da_2021_cop', id: hoveredFeatureId },
{ hover: true }
);
}
map.getCanvas().style.cursor = 'pointer';
}
});
// Mouse leave handler
map.on('mouseleave', 'my-layer', () => {
if (hoveredFeatureId !== null) {
map.setFeatureState(
{ source: 'my-vector-tiles', sourceLayer: 'da_2021_cop', id: hoveredFeatureId },
{ hover: false }
);
hoveredFeatureId = null;
}
map.getCanvas().style.cursor = '';
});
}
// Search functionality
const searchInput = document.getElementById('fieldSearch');
const searchResults = document.getElementById('searchResults');
const currentFieldDiv = document.getElementById('currentField');
const recalculateBtn = document.getElementById('recalculateBtn');
const classificationInfo = document.getElementById('classificationInfo');
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
if (query.length === 0) {
searchResults.style.display = 'none';
return;
}
const filtered = availableFields.filter(field =>
field.toLowerCase().includes(query)
).slice(0, 5);
if (filtered.length === 0) {
searchResults.innerHTML = '<div class="search-item">No fields found</div>';
} else {
searchResults.innerHTML = filtered.map(field =>
`<div class="search-item" data-field="${field}">${field}</div>`
).join('');
}
searchResults.style.display = 'block';
});
searchResults.addEventListener('click', (e) => {
if (e.target.classList.contains('search-item')) {
const selectedField = e.target.getAttribute('data-field');
if (selectedField && selectedField !== currentField) {
currentField = selectedField;
currentFieldDiv.textContent = `Current field: ${currentField}`;
searchInput.value = '';
searchResults.style.display = 'none';
updateURL(currentField);
addVisualizationLayerWithCKMeans(currentField);
}
}
});
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-container')) {
searchResults.style.display = 'none';
}
});
recalculateBtn.addEventListener('click', () => {
recalculateClassesFromExtent();
});
function calculateCKMeansBreaks(features, field, numClasses = 5) {
const values = features
.map(f => f.properties[field])
.filter(v => v !== null && v !== undefined && !isNaN(v));
if (values.length === 0) return null;
const uniqueValues = [...new Set(values)].sort((a, b) => a - b);
const actualNumClasses = Math.min(numClasses, uniqueValues.length);
if (actualNumClasses === 1) {
return {
breaks: [uniqueValues[0], uniqueValues[0]],
colors: ['#ff0000'],
method: 'single-value',
numClasses: 1
};
}
try {
const clusters = ss.ckmeans(values, actualNumClasses);
const breaks = [];
for (let i = 0; i < clusters.length; i++) {
if (i === 0) {
breaks.push(Math.min(...clusters[i]));
}
breaks.push(Math.max(...clusters[i]));
}
const colors = generateColors(actualNumClasses);
return {
breaks: breaks,
colors: colors,
method: 'ckmeans',
numClasses: actualNumClasses,
clusters: clusters.length
};
} catch (error) {
console.warn('CKMeans failed, falling back to quantile classification:', error);
return calculateQuantileBreaks(values, actualNumClasses);
}
}
function calculateQuantileBreaks(values, numClasses) {
const sortedValues = [...values].sort((a, b) => a - b);
const breaks = [];
const colors = generateColors(numClasses);
for (let i = 0; i <= numClasses; i++) {
const quantile = i / numClasses;
const index = Math.floor(quantile * (sortedValues.length - 1));
breaks.push(sortedValues[index]);
}
return {
breaks: breaks,
colors: colors,
method: 'quantile-fallback',
numClasses: numClasses
};
}
function generateColors(numClasses) {
const colors = [];
for (let i = 0; i < numClasses; i++) {
const intensity = (i + 1) / numClasses;
const red = Math.round(255);
const green = Math.round(255 * (1 - intensity));
const blue = Math.round(255 * (1 - intensity));
colors.push(`rgb(${red}, ${green}, ${blue})`);
}
return colors;
}
function addVisualizationLayerWithCKMeans(field) {
if (!mapLoaded) return;
// Update temporary color
map.setPaintProperty('my-layer', 'fill-color', '#cccccc');
updateClassificationInfo('Loading CKMeans...', 'calculating optimal classes');
setTimeout(() => {
recalculateClassesFromExtent();
}, 500);
}
function recalculateClassesFromExtent() {
if (!mapLoaded) return;
const features = map.queryRenderedFeatures({ layers: ['my-layer'] });
if (features.length > 0) {
console.log(`Calculating CKMeans classification for ${features.length} features`);
const actualFieldName = `count_${currentField}`;
const classification = calculateCKMeansBreaks(features, actualFieldName, 5);
if (classification) {
currentClassification = classification;
const paintExpression = ['case'];
for (let i = 0; i < classification.breaks.length - 1; i++) {
const lowerBound = classification.breaks[i];
const upperBound = classification.breaks[i + 1];
if (i === 0) {
paintExpression.push(['<=', ['get', actualFieldName], upperBound]);
} else {
paintExpression.push([
'all',
['>', ['get', actualFieldName], lowerBound],
['<=', ['get', actualFieldName], upperBound]
]);
}
paintExpression.push(classification.colors[i]);
}
paintExpression.push('#cccccc');
map.setPaintProperty('my-layer', 'fill-color', paintExpression);
updateLegend(currentField, classification.breaks, classification.colors);
updateClassificationInfo(
`${classification.method} (${classification.numClasses} classes)`,
`${features.length} features analyzed`
);
console.log('Classification applied:', classification);
}
} else {
console.log('No features in current view, trying to get data for initial classification...');
updateClassificationInfo('CKMeans (5 classes)', 'Pan/zoom to data areas for classification');
}
}
function updateLegend(field, breaks, colors) {
const legendContent = document.getElementById('legendContent');
let legendHTML = '';
for (let i = 0; i < colors.length && i < breaks.length - 1; i++) {
const rangeStart = breaks[i];
const rangeEnd = breaks[i + 1];
let label;
if (i === 0) {
label = `${rangeEnd.toFixed(0)}`;
} else {
label = `${rangeStart.toFixed(0)} - ${rangeEnd.toFixed(0)}`;
}
legendHTML += `
<div class="legend-item">
<div class="legend-color" style="background-color: ${colors[i]}"></div>
<span>${label}</span>
</div>
`;
}
legendContent.innerHTML = legendHTML;
}
function updateClassificationInfo(method, details) {
classificationInfo.innerHTML = `Classification: ${method}<br><small>${details}</small>`;
}
window.addEventListener('popstate', (e) => {
const newField = getFieldFromURL();
if (newField !== currentField && availableFields.includes(newField)) {
currentField = newField;
document.getElementById('currentField').textContent = `Current field: ${currentField}`;
addVisualizationLayerWithCKMeans(currentField);
}
});
map.on('click', 'my-layer', (e) => {
if (e.features.length > 0) {
const feature = e.features[0];
const properties = feature.properties;
console.log(`Field: ${currentField}, Value: ${properties[`count_${currentField}`]}`);
console.log('DGUID:', properties.da_dguid);
const clickLatLong = e.lngLat.wrap();
const googleMapsURL = `https://www.google.ca/maps/@${clickLatLong.lat},${clickLatLong.lng},17z`;
const openstreetmapURL = `https://www.openstreetmap.org/#map=17/${clickLatLong.lat}/${clickLatLong.lng}`;
const bingURL = `https://www.bing.com/maps?FORM=Z9LH2&cp=${clickLatLong.lat}%7E${clickLatLong.lng}&lvl=16.0`;
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(`
<div style="font-size: 12px;">
<strong>${currentField}:</strong> ${properties[`count_${currentField}`]}<br>
<strong>DGUID:</strong> ${properties.da_dguid}<br>
<strong>Google Maps:</strong> <a href="${googleMapsURL}" target="_blank">Open</a><br>
<strong>OpenStreetMap:</strong> <a href="${openstreetmapURL}" target="_blank">Open</a><br>
<strong>Bing:</strong> <a href="${bingURL}" target="_blank">Open</a>
</div>
`)
.addTo(map);
}
});
@@ -0,0 +1,313 @@
/**
* Pelias Geocoder for MapLibre GL JS
* Adapted from pelias-mapbox-gl-js for MapLibre compatibility
*/
import maplibregl from 'maplibre-gl';
class PeliasGeocoder {
constructor(options = {}) {
this.options = {
url: options.url || 'https://geocoder.alpha.phac.gc.ca/api/v1',
apiKey: options.apiKey || '',
params: options.params || {},
flyTo: options.flyTo === false ? false : (options.flyTo || {}),
wof: options.wof === false ? false : true,
marker: options.marker === false ? false : (options.marker || {}),
placeholder: options.placeholder || 'Search',
minLength: options.minLength || 3,
limit: options.limit || 5,
customAttribution: options.customAttribution || null
};
this.marker = null;
this.results = [];
this.selectedIndex = -1;
this.lastQuery = '';
this.searchTimeout = null;
}
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'maplibregl-ctrl pelias-ctrl';
// Create input
this.input = document.createElement('input');
this.input.type = 'text';
this.input.className = 'pelias-ctrl-input';
this.input.placeholder = this.options.placeholder;
// Create clear button
this.clearBtn = document.createElement('button');
this.clearBtn.className = 'pelias-ctrl-clear';
this.clearBtn.innerHTML = '×';
this.clearBtn.style.display = 'none';
// Create results container
this.resultsContainer = document.createElement('div');
this.resultsContainer.className = 'pelias-ctrl-results';
// Assemble the control
this.container.appendChild(this.input);
this.container.appendChild(this.clearBtn);
this.container.appendChild(this.resultsContainer);
// Set up event listeners
this.setupEventListeners();
return this.container;
}
onRemove() {
if (this.marker) {
this.marker.remove();
}
this.container.parentNode.removeChild(this.container);
this.map = undefined;
}
setupEventListeners() {
// Input events
this.input.addEventListener('input', (e) => {
const query = e.target.value;
if (query.length >= this.options.minLength) {
this.clearBtn.style.display = 'block';
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.search(query);
}, 300);
} else {
this.clearBtn.style.display = 'none';
this.clearResults();
}
});
// Keyboard navigation
this.input.addEventListener('keydown', (e) => {
if (!this.results.length) return;
switch(e.key) {
case 'ArrowDown':
e.preventDefault();
this.selectResult(Math.min(this.selectedIndex + 1, this.results.length - 1));
break;
case 'ArrowUp':
e.preventDefault();
this.selectResult(Math.max(this.selectedIndex - 1, -1));
break;
case 'Enter':
e.preventDefault();
if (this.selectedIndex >= 0) {
this.chooseResult(this.results[this.selectedIndex]);
}
break;
case 'Escape':
this.clearResults();
this.input.blur();
break;
}
});
// Clear button
this.clearBtn.addEventListener('click', () => {
this.input.value = '';
this.clearBtn.style.display = 'none';
this.clearResults();
if (this.marker) {
this.marker.remove();
this.marker = null;
}
this.input.focus();
});
// Click outside to close
document.addEventListener('click', (e) => {
if (!this.container.contains(e.target)) {
this.clearResults();
}
});
}
async search(query) {
if (query === this.lastQuery) return;
this.lastQuery = query;
const params = new URLSearchParams({
text: query,
size: this.options.limit,
...this.options.params
});
if (this.options.apiKey) {
params.append('api_key', this.options.apiKey);
}
// Add focus point if map has a center
if (this.map) {
const center = this.map.getCenter();
params.append('focus.point.lat', center.lat);
params.append('focus.point.lon', center.lng);
}
try {
const response = await fetch(`${this.options.url}/search?${params}`);
const data = await response.json();
if (data.features) {
this.results = data.features;
this.displayResults();
}
} catch (error) {
console.error('Geocoding error:', error);
this.showError('Search failed. Please try again.');
}
}
displayResults() {
this.resultsContainer.innerHTML = '';
this.selectedIndex = -1;
if (this.results.length === 0) {
const noResults = document.createElement('div');
noResults.className = 'pelias-ctrl-result';
noResults.textContent = 'No results found';
this.resultsContainer.appendChild(noResults);
} else {
this.results.forEach((result, index) => {
const item = document.createElement('div');
item.className = 'pelias-ctrl-result';
const name = document.createElement('div');
name.className = 'pelias-ctrl-result-name';
name.textContent = result.properties.name || result.properties.label;
const address = document.createElement('div');
address.className = 'pelias-ctrl-result-address';
// Build address from properties
const parts = [];
if (result.properties.locality) parts.push(result.properties.locality);
if (result.properties.region) parts.push(result.properties.region);
if (result.properties.country) parts.push(result.properties.country);
address.textContent = parts.join(', ');
item.appendChild(name);
if (address.textContent) {
item.appendChild(address);
}
item.addEventListener('click', () => {
this.chooseResult(result);
});
item.addEventListener('mouseenter', () => {
this.selectResult(index);
});
this.resultsContainer.appendChild(item);
});
}
this.resultsContainer.classList.add('active');
}
selectResult(index) {
// Remove previous selection
const items = this.resultsContainer.querySelectorAll('.pelias-ctrl-result');
items.forEach((item, i) => {
if (i === index) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
this.selectedIndex = index;
}
chooseResult(result) {
// Update input
this.input.value = result.properties.label || result.properties.name;
// Clear results
this.clearResults();
// Get coordinates
const coords = result.geometry.coordinates;
// Fly to location
if (this.options.flyTo !== false && this.map) {
const flyOptions = {
center: coords,
zoom: 16,
...this.options.flyTo
};
this.map.flyTo(flyOptions);
}
// Add/update marker
if (this.options.marker !== false && this.map) {
if (this.marker) {
this.marker.setLngLat(coords);
} else {
const markerOptions = {
color: '#FF0000',
...this.options.marker
};
this.marker = new maplibregl.Marker(markerOptions)
.setLngLat(coords)
.addTo(this.map);
}
}
// Trigger custom event
this.container.dispatchEvent(new CustomEvent('select', {
detail: result
}));
}
clearResults() {
this.resultsContainer.innerHTML = '';
this.resultsContainer.classList.remove('active');
this.results = [];
this.selectedIndex = -1;
}
showError(message) {
this.resultsContainer.innerHTML = '';
const error = document.createElement('div');
error.className = 'pelias-ctrl-error';
error.textContent = message;
this.resultsContainer.appendChild(error);
this.resultsContainer.classList.add('active');
}
// Public methods
setQuery(query) {
this.input.value = query;
if (query.length >= this.options.minLength) {
this.search(query);
}
}
clear() {
this.input.value = '';
this.clearBtn.style.display = 'none';
this.clearResults();
if (this.marker) {
this.marker.remove();
this.marker = null;
}
}
focus() {
this.input.focus();
}
blur() {
this.input.blur();
}
}
export default PeliasGeocoder;
@@ -0,0 +1,256 @@
@import 'maplibre-gl/dist/maplibre-gl.css';
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
html,
body,
#map {
height: 100%;
}
.search-container {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
width: 300px;
}
.search-input {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 14px;
outline: none;
box-sizing: border-box;
}
.search-results {
max-height: 200px;
overflow-y: auto;
border-top: 1px solid #eee;
display: none;
}
.search-item {
padding: 10px 12px;
cursor: pointer;
border-bottom: 1px solid #f5f5f5;
font-size: 13px;
}
.search-item:hover {
background-color: #f0f0f0;
}
.search-item:last-child {
border-bottom: none;
}
.recalculate-btn {
position: absolute;
bottom: 10px;
left: 10px;
z-index: 1000;
background: #007cbf;
color: white;
border: none;
padding: 10px 15px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.recalculate-btn:hover {
background: #005a87;
}
.current-field {
position: absolute;
top: 70px;
right: 10px;
z-index: 1000;
background: rgba(255, 255, 255, 0.95);
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
color: #333;
max-width: 300px;
word-break: break-word;
}
.legend {
position: absolute;
bottom: 20px;
right: 10px;
z-index: 1000;
background: rgba(255, 255, 255, 0.95);
border-radius: 6px;
padding: 12px;
font-size: 12px;
min-width: 120px;
}
.legend-title {
font-weight: bold;
margin-bottom: 8px;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 4px;
}
.legend-color {
width: 16px;
height: 16px;
margin-right: 8px;
border: 1px solid #ccc;
}
.classification-info {
position: absolute;
bottom: 50px;
left: 10px;
z-index: 1000;
background: rgba(255, 255, 255, 0.95);
padding: 8px 12px;
border-radius: 6px;
font-size: 11px;
color: #666;
max-width: 200px;
}
/* Pelias Geocoder Styles - Adapted for MapLibre */
.pelias-ctrl {
position: relative;
display: inline-block;
background: #fff;
border-radius: 3px;
box-shadow: 0 0 10px 2px rgba(0, 0, 0, .1);
min-width: 200px;
max-width: 360px;
}
.pelias-ctrl-input {
width: 100%;
border: 0;
font-size: 12px;
line-height: 1.67;
padding: 8px 32px 8px 8px;
text-overflow: ellipsis;
background: transparent;
outline: none;
box-sizing: border-box;
}
.pelias-ctrl-input::-webkit-input-placeholder {
color: #9b9b9b;
}
.pelias-ctrl-input::-moz-placeholder {
color: #9b9b9b;
}
.pelias-ctrl-input:-ms-input-placeholder {
color: #9b9b9b;
}
.pelias-ctrl-input:-moz-placeholder {
color: #9b9b9b;
}
.pelias-ctrl-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border-top: 1px solid #eee;
border-radius: 0 0 3px 3px;
box-shadow: 0 0 10px 2px rgba(0, 0, 0, .1);
z-index: 1000;
max-height: 200px;
overflow-y: auto;
display: none;
}
.pelias-ctrl-results.active {
display: block;
}
.pelias-ctrl-result {
padding: 8px;
cursor: pointer;
border-bottom: 1px solid #eee;
font-size: 12px;
line-height: 1.4;
}
.pelias-ctrl-result:last-child {
border-bottom: none;
}
.pelias-ctrl-result:hover,
.pelias-ctrl-result.active {
background: #f8f8f8;
}
.pelias-ctrl-result-name {
font-weight: bold;
margin-bottom: 2px;
}
.pelias-ctrl-result-address {
color: #666;
}
.pelias-ctrl-clear {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
font-size: 16px;
color: #999;
padding: 0;
width: 20px;
height: 20px;
display: none;
}
.pelias-ctrl-clear:hover {
color: #333;
}
.pelias-ctrl-clear.active {
display: block;
}
.pelias-ctrl-error {
padding: 8px;
color: #d00;
font-size: 11px;
border-top: 1px solid #eee;
}
/* MapLibre Control positioning */
.maplibregl-ctrl-top-right .pelias-ctrl {
margin: 10px 10px 0 0;
}
.maplibregl-ctrl-top-left .pelias-ctrl {
margin: 10px 0 0 10px;
}
@@ -0,0 +1 @@
rclone --progress copy dist/ cloudflare:/diegoripley-www/files/census_of_population_vector_tiles_subset_august_12_2025/
@@ -0,0 +1,23 @@
import { defineConfig } from 'vite'
export default defineConfig({
base: './',
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'maplibre': ['maplibre-gl'],
'statistics': ['simple-statistics']
}
}
}
},
server: {
port: 3000,
host: "0.0.0.0",
open: true
}
})