mirror of
https://github.com/hyperknot/openfreemap.git
synced 2026-05-21 14:02:15 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df4711fc55 | ||
|
|
afc204d8c5 | ||
|
|
68d820f4d1 | ||
|
|
b6d26605e3 | ||
|
|
c4aecd01f6 | ||
|
|
ca337345f2 | ||
|
|
3cec51d0b1 | ||
|
|
f12ffcb032 | ||
|
|
f2161e868d | ||
|
|
cd76d94aac | ||
|
|
0dc7551eca | ||
|
|
b43a1f5830 | ||
|
|
b06f5f248f | ||
|
|
bdb142d9ec | ||
|
|
fff93d5146 | ||
|
|
722a87a737 | ||
|
|
fa2f0d14cd | ||
|
|
f91dc2aaa3 | ||
|
|
d46e26e971 | ||
|
|
6cf7ddc672 | ||
|
|
8ce37a96b2 | ||
|
|
24e1e636b9 | ||
|
|
c75a87b151 |
11
biome.jsonc
11
biome.jsonc
@@ -1,13 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"indentStyle": "space",
|
"indentStyle": "space",
|
||||||
"lineWidth": 100
|
"lineWidth": 100
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||||
"enabled": true,
|
|
||||||
"ignore": []
|
|
||||||
},
|
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
@@ -16,7 +13,7 @@
|
|||||||
"noForEach": "off"
|
"noForEach": "off"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ignore": []
|
"includes": ["**"]
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
@@ -26,6 +23,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"maxSize": 100000,
|
"maxSize": 100000,
|
||||||
"ignore": ["venv", "dist", ".astro"]
|
"includes": ["**", "!**/venv", "!**/dist", "!**/.astro"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,28 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"clean": "rm -rf .astro dist ___node_modules/.astro ",
|
||||||
"start": "astro dev",
|
"dev": "pnpm clean; astro dev",
|
||||||
"build": "astro build",
|
"build": "pnpm clean; astro check && pnpm tsc && astro build",
|
||||||
"preview": "astro preview",
|
"preview": "pnpm build && astro preview",
|
||||||
"astro": "astro"
|
"preview_w": "pnpm build && wrangler dev",
|
||||||
|
"deploy_w": "pnpm build && wrangler deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.2.1",
|
||||||
"astro": "^5.4.0",
|
"astro": "^5.4.0",
|
||||||
"lightningcss": "^1.29.1"
|
"lightningcss": "^1.29.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": ["esbuild", "sharp"]
|
"onlyBuiltDependencies": [
|
||||||
|
"esbuild",
|
||||||
|
"sharp",
|
||||||
|
"workerd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"wrangler": "^4.43.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1307
website/pnpm-lock.yaml
generated
1307
website/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
15
website/public/debug/lang/colon.html
Normal file
15
website/public/debug/lang/colon.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>OpenFreeMap Debug</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" class="w-full h-screen"></div>
|
||||||
|
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
||||||
|
<script src="colon.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
65
website/public/debug/lang/colon.js
Normal file
65
website/public/debug/lang/colon.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: 'map',
|
||||||
|
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
function modifyStyle({ style, langCode }) {
|
||||||
|
if (!langCode) {
|
||||||
|
langCode = 'en'
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const layer of style.layers) {
|
||||||
|
if (layer.source !== 'openmaptiles') continue
|
||||||
|
if (!layer.layout) continue
|
||||||
|
|
||||||
|
const textField = layer.layout['text-field']
|
||||||
|
if (!textField) continue
|
||||||
|
|
||||||
|
// highway numbers, etc. - skip ref-only fields
|
||||||
|
if (JSON.stringify(textField) === JSON.stringify(['to-string', ['get', 'ref']])) continue
|
||||||
|
|
||||||
|
const nameUnderscore = `name_${langCode}`
|
||||||
|
const nameColon = `name:${langCode}`
|
||||||
|
|
||||||
|
// Always display both values
|
||||||
|
layer.layout['text-field'] = ['concat', ['get', nameUnderscore], '\n', ['get', nameColon]]
|
||||||
|
|
||||||
|
// Color red when they are different
|
||||||
|
if (!layer.paint) layer.paint = {}
|
||||||
|
layer.paint['text-color'] = [
|
||||||
|
'case',
|
||||||
|
['!=', ['get', nameUnderscore], ['get', nameColon]],
|
||||||
|
'#ff0000', // Red when different
|
||||||
|
'#000000', // Default color when same (adjust as needed)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyLanguage() {
|
||||||
|
const hash = window.location.hash.substring(1) // Remove the '#'
|
||||||
|
const langCode = hash || null
|
||||||
|
|
||||||
|
const style = map.getStyle()
|
||||||
|
modifyStyle({ style, langCode })
|
||||||
|
map.setStyle(style, { diff: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
map.on('load', () => {
|
||||||
|
// Add default hash if not present
|
||||||
|
if (!window.location.hash) {
|
||||||
|
alert(
|
||||||
|
'To change the map language, modify the language code in the URL #.\nLabels will be RED when different.\nname_xx on line 1, name:xx on line 2',
|
||||||
|
)
|
||||||
|
window.location.hash = '#en'
|
||||||
|
// The hashchange event will trigger applyLanguage()
|
||||||
|
} else {
|
||||||
|
applyLanguage()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen for hash changes in URL
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
applyLanguage()
|
||||||
|
})
|
||||||
114
website/public/debug/lang/mix.html
Normal file
114
website/public/debug/lang/mix.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>OpenFreeMap Debug</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body class="flex flex-col h-screen">
|
||||||
|
<!-- UI Panel -->
|
||||||
|
<div class="bg-gray-900 border-b border-gray-700 shadow-md">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 py-2">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="flex-1">
|
||||||
|
<label for="line1" class="block text-xs font-medium text-gray-400 mb-1"> Line 1 </label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="line1"
|
||||||
|
class="w-full px-3 py-1.5 text-sm border border-gray-600 rounded bg-gray-800 text-gray-100 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all font-mono"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="line1-expr"
|
||||||
|
readonly
|
||||||
|
class="w-full px-3 py-1 text-xs border border-gray-700 rounded bg-gray-900 text-gray-400 font-mono mt-1 cursor-default focus:outline-none focus:ring-0 focus:border-gray-700 selection:bg-gray-700 selection:text-gray-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<label for="line2" class="block text-xs font-medium text-gray-400 mb-1"> Line 2 </label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="line2"
|
||||||
|
class="w-full px-3 py-1.5 text-sm border border-gray-600 rounded bg-gray-800 text-gray-100 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all font-mono"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="line2-expr"
|
||||||
|
readonly
|
||||||
|
class="w-full px-3 py-1 text-xs border border-gray-700 rounded bg-gray-900 text-gray-400 font-mono mt-1 cursor-default focus:outline-none focus:ring-0 focus:border-gray-700 selection:bg-gray-700 selection:text-gray-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<div class="w-24">
|
||||||
|
<label for="lang" class="block text-xs font-medium text-gray-400 mb-1">
|
||||||
|
Lang
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lang"
|
||||||
|
class="w-full px-3 py-1.5 text-sm border border-gray-600 rounded bg-gray-800 text-gray-100 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all font-mono text-center"
|
||||||
|
maxlength="5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
id="shareBtn"
|
||||||
|
class="px-4 py-1.5 text-sm font-medium text-white bg-blue-600 rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900 transition-all mt-5"
|
||||||
|
>
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="resetBtn"
|
||||||
|
class="px-4 py-1.5 text-sm font-medium text-white bg-gray-700 rounded hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-900 transition-all mt-5"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label
|
||||||
|
class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="showDifferencesRed"
|
||||||
|
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
|
||||||
|
/>
|
||||||
|
<span>highlight line1 != line2</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="map" class="flex-1"></div>
|
||||||
|
|
||||||
|
<!-- Modal Dialog -->
|
||||||
|
<div id="shareModal" class="hidden fixed inset-0 z-50">
|
||||||
|
<div id="modalOverlay" class="fixed inset-0 bg-black/50"></div>
|
||||||
|
<div class="fixed inset-0 flex items-center justify-center p-4">
|
||||||
|
<div class="relative bg-gray-800 rounded-lg p-8 max-w-md shadow-2xl">
|
||||||
|
<p class="text-gray-300 text-center leading-relaxed mb-6">
|
||||||
|
Your settings have been saved to the URL.<br />
|
||||||
|
Copy the address bar to share this map.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
id="closeModalBtn"
|
||||||
|
class="w-full py-2.5 text-sm text-white bg-gray-700 rounded hover:bg-gray-600 transition-all"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
||||||
|
<script src="mix.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
240
website/public/debug/lang/mix.js
Normal file
240
website/public/debug/lang/mix.js
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// ============================================
|
||||||
|
// 1. MAIN EXECUTION (Entry Point)
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: 'map',
|
||||||
|
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 2,
|
||||||
|
hash: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const line1Input = document.getElementById('line1')
|
||||||
|
const line2Input = document.getElementById('line2')
|
||||||
|
const langInput = document.getElementById('lang')
|
||||||
|
const line1ExprInput = document.getElementById('line1-expr')
|
||||||
|
const line2ExprInput = document.getElementById('line2-expr')
|
||||||
|
const showDifferencesRedCheckbox = document.getElementById('showDifferencesRed')
|
||||||
|
|
||||||
|
map.on('load', () => {
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
|
||||||
|
// Set defaults if no params present
|
||||||
|
if (!params.has('line1') && !params.has('line2') && !params.has('lang')) {
|
||||||
|
const url = new URL(window.location)
|
||||||
|
url.searchParams.set('line1', 'colon,underscore,latin,name')
|
||||||
|
url.searchParams.set('line2', 'nonlatin')
|
||||||
|
url.searchParams.set('lang', 'en')
|
||||||
|
window.history.replaceState({}, '', url)
|
||||||
|
}
|
||||||
|
syncInputsFromParams()
|
||||||
|
applyConfiguration()
|
||||||
|
initializeInputListeners()
|
||||||
|
initializeModal()
|
||||||
|
initializeResetButton()
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 2. UI INITIALIZATION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
function initializeInputListeners() {
|
||||||
|
const debouncedApplyConfig = debounce(applyConfiguration, 500)
|
||||||
|
|
||||||
|
const handleInput = () => {
|
||||||
|
updateParamsFromInputs()
|
||||||
|
updateExpressionDisplays()
|
||||||
|
debouncedApplyConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
line1Input.addEventListener('input', handleInput)
|
||||||
|
line2Input.addEventListener('input', handleInput)
|
||||||
|
langInput.addEventListener('input', handleInput)
|
||||||
|
showDifferencesRedCheckbox.addEventListener('change', handleInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeModal() {
|
||||||
|
const modal = document.getElementById('shareModal')
|
||||||
|
|
||||||
|
document.getElementById('shareBtn').addEventListener('click', () => {
|
||||||
|
modal.classList.remove('hidden')
|
||||||
|
})
|
||||||
|
|
||||||
|
document.getElementById('closeModalBtn').addEventListener('click', () => {
|
||||||
|
modal.classList.add('hidden')
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
|
||||||
|
modal.classList.add('hidden')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeResetButton() {
|
||||||
|
document.getElementById('resetBtn').addEventListener('click', () => {
|
||||||
|
const hash = window.location.hash
|
||||||
|
window.location.href = `${window.location.pathname}${hash}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 3. CONFIGURATION & SYNC
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
function applyConfiguration() {
|
||||||
|
const { line1, line2, lang, showDifferencesRed } = parseParams()
|
||||||
|
|
||||||
|
if (!map.getStyle()) return
|
||||||
|
|
||||||
|
const style = map.getStyle()
|
||||||
|
modifyStyle({
|
||||||
|
style,
|
||||||
|
line1Config: line1 ?? '',
|
||||||
|
line2Config: line2 ?? '',
|
||||||
|
langCode: lang,
|
||||||
|
showDifferencesRed,
|
||||||
|
})
|
||||||
|
map.setStyle(style, { diff: true })
|
||||||
|
updateExpressionDisplays()
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncInputsFromParams() {
|
||||||
|
const { line1, line2, lang, showDifferencesRed } = parseParams()
|
||||||
|
line1Input.value = line1 ?? ''
|
||||||
|
line2Input.value = line2 ?? ''
|
||||||
|
langInput.value = lang ?? ''
|
||||||
|
showDifferencesRedCheckbox.checked = showDifferencesRed
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateParamsFromInputs() {
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
params.set('line1', line1Input.value)
|
||||||
|
params.set('line2', line2Input.value)
|
||||||
|
params.set('lang', langInput.value)
|
||||||
|
|
||||||
|
if (showDifferencesRedCheckbox.checked) {
|
||||||
|
params.set('showDifferencesRed', '1')
|
||||||
|
} else {
|
||||||
|
params.delete('showDifferencesRed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = params.toString()
|
||||||
|
const hash = window.location.hash
|
||||||
|
const newUrl = `${window.location.pathname}?${queryString}${hash}`
|
||||||
|
|
||||||
|
window.history.replaceState({}, '', newUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExpressionDisplays() {
|
||||||
|
const { line1, line2, lang } = parseParams()
|
||||||
|
const langCode = lang
|
||||||
|
|
||||||
|
const line1Expr = buildFieldAccessor(line1 ?? '', langCode)
|
||||||
|
const line2Expr = buildFieldAccessor(line2 ?? '', langCode)
|
||||||
|
|
||||||
|
line1ExprInput.value = line1Expr ? JSON.stringify(line1Expr) : ''
|
||||||
|
line2ExprInput.value = line2Expr ? JSON.stringify(line2Expr) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 4. STYLE MODIFICATION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
function modifyStyle({ style, line1Config, line2Config, langCode, showDifferencesRed }) {
|
||||||
|
for (const layer of style.layers) {
|
||||||
|
if (layer.source !== 'openmaptiles') continue
|
||||||
|
if (!layer.layout) continue
|
||||||
|
|
||||||
|
const textField = layer.layout['text-field']
|
||||||
|
if (!textField) continue
|
||||||
|
|
||||||
|
if (JSON.stringify(textField) === JSON.stringify(['to-string', ['get', 'ref']])) continue
|
||||||
|
|
||||||
|
const id = layer.id
|
||||||
|
const separator = id.includes('line') || id.includes('highway') ? ' ' : '\n'
|
||||||
|
|
||||||
|
const line1Expr = buildFieldAccessor(line1Config, langCode)
|
||||||
|
const line2Expr = buildFieldAccessor(line2Config, langCode)
|
||||||
|
|
||||||
|
if (line1Expr && line2Expr) {
|
||||||
|
layer.layout['text-field'] = ['concat', line1Expr, separator, line2Expr]
|
||||||
|
} else if (line1Expr) {
|
||||||
|
layer.layout['text-field'] = line1Expr
|
||||||
|
} else if (line2Expr) {
|
||||||
|
layer.layout['text-field'] = line2Expr
|
||||||
|
} else {
|
||||||
|
layer.layout['text-field'] = ['get', 'name']
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply red color when differences should be shown
|
||||||
|
if (showDifferencesRed && line1Expr && line2Expr) {
|
||||||
|
if (!layer.paint) layer.paint = {}
|
||||||
|
layer.paint['text-color'] = [
|
||||||
|
'case',
|
||||||
|
['!=', line1Expr, line2Expr],
|
||||||
|
'#ff0000', // Red when different
|
||||||
|
'#000000', // Black when same
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
// Reset to default color if checkbox is unchecked
|
||||||
|
if (layer.paint && layer.paint['text-color']) {
|
||||||
|
delete layer.paint['text-color']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 5. UTILITY FUNCTIONS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
function debounce(func, delay) {
|
||||||
|
let timeoutId
|
||||||
|
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
func.apply(this, args)
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseParams() {
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
return {
|
||||||
|
line1: params.get('line1'),
|
||||||
|
line2: params.get('line2'),
|
||||||
|
lang: params.get('lang') || 'en',
|
||||||
|
showDifferencesRed: params.has('showDifferencesRed'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFieldAccessor(config, langCode) {
|
||||||
|
if (!config) return null
|
||||||
|
|
||||||
|
const parts = []
|
||||||
|
const fields = config
|
||||||
|
.split(',')
|
||||||
|
.map(f => f.trim())
|
||||||
|
.filter(f => f)
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
if (field === 'underscore') {
|
||||||
|
parts.push(['get', `name_${langCode}`])
|
||||||
|
} else if (field === 'colon') {
|
||||||
|
parts.push(['get', `name:${langCode}`])
|
||||||
|
} else if (field === 'latin') {
|
||||||
|
parts.push(['get', 'name:latin'])
|
||||||
|
} else if (field === 'nonlatin') {
|
||||||
|
parts.push(['get', 'name:nonlatin'])
|
||||||
|
} else if (field === 'name') {
|
||||||
|
parts.push(['get', 'name'])
|
||||||
|
} else {
|
||||||
|
parts.push(['get', field])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.length > 0 ? ['coalesce', ...parts] : null
|
||||||
|
}
|
||||||
15
website/public/debug/lang/params.html
Normal file
15
website/public/debug/lang/params.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>OpenFreeMap Debug</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" class="w-full h-screen"></div>
|
||||||
|
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
||||||
|
<script src="params.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
78
website/public/debug/lang/params.js
Normal file
78
website/public/debug/lang/params.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: 'map',
|
||||||
|
hash: 'map',
|
||||||
|
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
function modifyStyle({ style, langCode }) {
|
||||||
|
if (!langCode) {
|
||||||
|
langCode = 'en'
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const layer of style.layers) {
|
||||||
|
if (layer.source !== 'openmaptiles') continue
|
||||||
|
if (!layer.layout) continue
|
||||||
|
|
||||||
|
const textField = layer.layout['text-field']
|
||||||
|
if (!textField) continue
|
||||||
|
|
||||||
|
// highway numbers, etc. - skip ref-only fields
|
||||||
|
if (JSON.stringify(textField) === JSON.stringify(['to-string', ['get', 'ref']])) continue
|
||||||
|
|
||||||
|
const nameUnderscore = `name_${langCode}`
|
||||||
|
const nameColon = `name:${langCode}`
|
||||||
|
|
||||||
|
// Always display both values
|
||||||
|
layer.layout['text-field'] = ['concat', ['get', nameUnderscore], '\n', ['get', nameColon]]
|
||||||
|
|
||||||
|
// Color red when they are different
|
||||||
|
if (!layer.paint) layer.paint = {}
|
||||||
|
layer.paint['text-color'] = [
|
||||||
|
'case',
|
||||||
|
['!=', ['get', nameUnderscore], ['get', nameColon]],
|
||||||
|
'#ff0000', // Red when different
|
||||||
|
'#000000', // Default color when same (adjust as needed)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLanguageParam() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
return urlParams.get('lang')
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyLanguage() {
|
||||||
|
const langCode = getLanguageParam() || null
|
||||||
|
|
||||||
|
const style = map.getStyle()
|
||||||
|
modifyStyle({ style, langCode })
|
||||||
|
map.setStyle(style, { diff: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
map.on('load', () => {
|
||||||
|
const langCode = getLanguageParam()
|
||||||
|
|
||||||
|
// Alert the URL param value on first load
|
||||||
|
alert(
|
||||||
|
`Language parameter: ${langCode || 'not set (defaulting to en)'}\n\n` +
|
||||||
|
'To change the map language, modify the ?lang= parameter in the URL.\n' +
|
||||||
|
'Labels will be RED when different.\n' +
|
||||||
|
'name_xx on line 1, name:xx on line 2',
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add default param if not present
|
||||||
|
if (!langCode) {
|
||||||
|
const url = new URL(window.location)
|
||||||
|
url.searchParams.set('lang', 'en')
|
||||||
|
window.history.replaceState({}, '', url)
|
||||||
|
}
|
||||||
|
|
||||||
|
applyLanguage()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen for URL changes (e.g., browser back/forward)
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
applyLanguage()
|
||||||
|
})
|
||||||
15
website/public/debug/lang/switch.html
Normal file
15
website/public/debug/lang/switch.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>OpenFreeMap Debug</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" class="w-full h-screen"></div>
|
||||||
|
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
||||||
|
<script src="switch.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
78
website/public/debug/lang/switch.js
Normal file
78
website/public/debug/lang/switch.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: 'map',
|
||||||
|
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
function modifyStyle({ style, langCode }) {
|
||||||
|
if (!langCode) {
|
||||||
|
langCode = 'en'
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const layer of style.layers) {
|
||||||
|
if (layer.source !== 'openmaptiles') continue
|
||||||
|
if (!layer.layout) continue
|
||||||
|
|
||||||
|
const textField = layer.layout['text-field']
|
||||||
|
if (!textField) continue
|
||||||
|
|
||||||
|
// highway numbers, etc. - skip ref-only fields
|
||||||
|
if (JSON.stringify(textField) === JSON.stringify(['to-string', ['get', 'ref']])) continue
|
||||||
|
|
||||||
|
const id = layer.id
|
||||||
|
|
||||||
|
let separator
|
||||||
|
if (id.includes('line') || id.includes('highway')) {
|
||||||
|
separator = ' '
|
||||||
|
} else {
|
||||||
|
separator = '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// the default is "en", not "int"
|
||||||
|
let parts
|
||||||
|
if (langCode === 'int') {
|
||||||
|
parts = [['get', 'name']]
|
||||||
|
} else {
|
||||||
|
parts = [
|
||||||
|
['get', `name_${langCode}`],
|
||||||
|
['get', `name:${langCode}`],
|
||||||
|
['get', 'name'],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.layout['text-field'] = [
|
||||||
|
'case',
|
||||||
|
['has', 'name:nonlatin'],
|
||||||
|
['concat', ['get', 'name:latin'], separator, ['get', 'name:nonlatin']],
|
||||||
|
['coalesce', ...parts],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyLanguage() {
|
||||||
|
const hash = window.location.hash.substring(1) // Remove the '#'
|
||||||
|
const langCode = hash || null
|
||||||
|
|
||||||
|
const style = map.getStyle()
|
||||||
|
modifyStyle({ style, langCode })
|
||||||
|
map.setStyle(style, { diff: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
map.on('load', () => {
|
||||||
|
// Add default hash if not present
|
||||||
|
if (!window.location.hash) {
|
||||||
|
alert(
|
||||||
|
'To change the map language, modify the language code in the URL.\n\nExamples:\n• #en → English\n• #de → German\n• #fr → French\n• #es → Spanish\n• #int → International names',
|
||||||
|
)
|
||||||
|
window.location.hash = '#es'
|
||||||
|
// The hashchange event will trigger applyLanguage()
|
||||||
|
} else {
|
||||||
|
applyLanguage()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen for hash changes in URL
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
applyLanguage()
|
||||||
|
})
|
||||||
15
website/public/debug/terrain/terrain.html
Normal file
15
website/public/debug/terrain/terrain.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OpenFreeMap Debug</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" class="w-full h-screen"></div>
|
||||||
|
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
||||||
|
<script src="terrain.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
26
website/public/debug/terrain/terrain.js
Normal file
26
website/public/debug/terrain/terrain.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: 'map',
|
||||||
|
hash: 'map',
|
||||||
|
zoom: 10.5,
|
||||||
|
center: [9.0788, 47.1194],
|
||||||
|
style: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
hillshadeSource: {
|
||||||
|
type: 'raster-dem',
|
||||||
|
url: 'https://tiles.mapterhorn.com/tilejson.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: 'hillshade',
|
||||||
|
type: 'hillshade',
|
||||||
|
source: 'hillshadeSource',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
map.on('load', () => {
|
||||||
|
console.log('Terrain map loaded')
|
||||||
|
})
|
||||||
@@ -17,6 +17,8 @@ const { showStyleURL } = Astro.props
|
|||||||
<button data-style="positron" class="btn">Positron</button>
|
<button data-style="positron" class="btn">Positron</button>
|
||||||
<button data-style="bright" class="btn">Bright</button>
|
<button data-style="bright" class="btn">Bright</button>
|
||||||
<button data-style="liberty" class="btn selected">Liberty</button>
|
<button data-style="liberty" class="btn selected">Liberty</button>
|
||||||
|
<button data-style="dark" class="btn">Dark</button>
|
||||||
|
<button data-style="fiord" class="btn">Fiord</button>
|
||||||
<button data-style="liberty-3d" class="btn">3D</button>
|
<button data-style="liberty-3d" class="btn">3D</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
10
website/wrangler.jsonc
Normal file
10
website/wrangler.jsonc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "openfreemap-website",
|
||||||
|
"compatibility_date": "2025-10-17",
|
||||||
|
"assets": {
|
||||||
|
"directory": "./dist"
|
||||||
|
},
|
||||||
|
"account_id": "99fde2e5efdeb199c6910cdeaa276a97",
|
||||||
|
"workers_dev": false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user