Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions ROADMAP.he.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@

## פלטפורמה

- [ ] **גרסה 0.3.0 - מפת רקע וקטורית באחסון עצמי (Protomaps + MapLibre)** -
החלפת מפת הרקע הנוכחית (Israel Hiking Map, עמוסה ומציגה את הקו הירוק) במפה
וקטורית נקייה מבוססת Protomaps שמוצגת עם MapLibre. אנחנו מעצבים את המפה:
יישובים, כבישים ומים, **בלי שכבת גבולות**, עם שמות בשפת האתר (עברית באתר
העברי, אנגלית באתר האנגלי). כולל **שכבת שטחי A/B/C**. קובץ המפה מאוחסן אצלנו
ב-Cloudflare R2. ההיתכנות הוכחה עם אב-טיפוס עובד. זה מחליף את הרעיון של מפת
GovMap (שלא אושרה לנו לאחר שבוע).
- [ ] **PWA / עבודה לא מקוונת** - אפליקציה להתקנה שעובדת גם בלי אינטרנט.
- [ ] **מפת GovMap** - לאחר קבלת טוקן API של GovMap לדומיין, מעבר למנוע המפה של
GovMap כדי להשתמש במפת הרקע הרשמית של ממשלת ישראל. עד אז מפת הרקע בעברית
היא Israel Hiking Map.
- [ ] **ביקורת נגישות וביצועים מלאה** לפני השקה רחבה.

## תשתית
Expand Down
22 changes: 14 additions & 8 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,22 @@ Contributions welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).

## Platform

- [ ] **v0.3.0 - self-hosted vector basemap (Protomaps + MapLibre)** - replace
the Leaflet + Israel Hiking Map basemap (cluttered, shows the green line)
with a self-hosted Protomaps PMTiles basemap rendered by MapLibre GL. The
style is authored in-house: a clean map (places, roads, water), **no
boundaries layer**, labels that follow the UI language (Hebrew `name:he`
in the Hebrew site, English `name:en` in the English site, RTL text
plugin for correct Hebrew shaping). Includes an **Area A/B/C overlay**
layer. The tile file is served first-party from Cloudflare R2 (no 25 MiB
Workers-asset cap; range requests supported). Feasibility proven with a
working prototype. This supersedes the GovMap and keyed-basemap items
below, which are no longer needed (GovMap access was not granted after a
week). Remaining work: MapLibre migration (markers, popups, locate,
language switch), self-host glyphs, source the Area A/B/C GeoJSON, build
and upload the Israel PMTiles to R2, `worker-src blob:` CSP addition.
Comment on lines +47 to +54

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep both roadmap entries consistent with the prototype status.

Both entries describe Area A/B/C and R2 as delivered, but design/maplibre-prototype/prototype.html has no overlay and still uses a localhost PMTiles URL. design/maplibre-prototype/README.md lists these items as production work.

  • ROADMAP.md#L47-L54: describe the Area A/B/C overlay and R2 hosting as planned work.
  • ROADMAP.he.md#L37-L40: use equivalent planned wording for the overlay and R2 hosting.
📍 Affects 2 files
  • ROADMAP.md#L47-L54 (this comment)
  • ROADMAP.he.md#L37-L40
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ROADMAP.md` around lines 47 - 54, Update the roadmap entries to reflect that
Area A/B/C overlay support and R2 PMTiles hosting are planned, not delivered. In
ROADMAP.md lines 47-54, revise the prototype status and remaining-work wording
accordingly; apply equivalent planned wording in ROADMAP.he.md lines 37-40,
preserving consistency between both entries.

- [ ] **PWA / offline** - installable, works offline (the dataset is tiny),
app icon on mobile.
- [ ] **GovMap basemap (migration)** - once a domain-bound GovMap API token is
granted for `taxmap.nx1xlab.dev`, migrate the map engine from Leaflet to
the GovMap JS SDK (OpenLayers-based) to use the official Israeli
government basemap. Gated on the token; direct tile embedding is not
permitted. Until then the Hebrew basemap is Israel Hiking Map.
- [ ] **Optional keyed basemap** - a MapTiler/Mapbox layer for fully
localized labels at every zoom (current free tiles romanize only major
places in English; Hebrew via OSM is fully local).
- [ ] **Full accessibility + performance audit** - complete the automated
a11y/perf/SEO sweep before a public launch.

Expand Down
27 changes: 27 additions & 0 deletions design/maplibre-prototype/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# MapLibre + Protomaps basemap prototype (v0.3.0)

Proof-of-concept for replacing Leaflet + Israel Hiking Map with a self-hosted
vector basemap. Verified working:

- Self-hosted Israel PMTiles (extracted from the Protomaps global build with
`scripts/build-tiles.sh`) rendered by MapLibre GL.
- Clean style authored in-house: places, roads, water. **No boundaries layer**,
so no green line / 67 border.
- Labels follow the UI language: Hebrew (`name:he`) with the RTL text plugin
for correct shaping, English (`name:en`).

![Hebrew](preview-hebrew.png)
![English](preview-english.png)

`prototype.html` is a standalone page (loads MapLibre + pmtiles from a CDN and
a locally-served `israel.pmtiles`). It is a reference only, not wired into the
Comment on lines +16 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document a runnable local setup.

The prototype requests http://localhost:4401/israel.pmtiles, while scripts/build-tiles.sh writes tiles/israel.pmtiles. The README does not explain how to serve the page and tile at matching paths. Add the exact server command and URL, or change the prototype URL to match the documented layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/maplibre-prototype/README.md` around lines 16 - 17, Update the README
documentation around the prototype setup to provide a runnable local command
that serves the page and exposes tiles/israel.pmtiles at the expected
/israel.pmtiles path, including the exact browser URL; alternatively, change
prototype.html’s tile URL and document the matching layout. Ensure the
documented setup works with the output from scripts/build-tiles.sh.

app. The production integration (markers, popups, locate, language switch,
Area A/B/C overlay, R2 hosting, self-hosted glyphs, CSP) is the v0.3.0 work in
ROADMAP.md.

## Hosting

The Israel PMTiles is ~22 MB at zoom 12, ~45 MB at 13, ~90 MB at 14. Workers
static assets cap at 25 MiB per file, so the tile file is served from
Cloudflare R2 (no cap, HTTP range supported). Clients range-fetch only the few
KB of tiles they view, so file size affects hosting only, not user bandwidth.
Comment on lines +24 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
sed -n '1,80p' design/maplibre-prototype/README.md 2>/dev/null || true

echo
echo "== search for bandwidth/PMtiles/cache statements =="
rg -n "bandwidth|PMTiles|Workers|static assets|Cloudflare R2|range-fetch|-fetch" design/maplibre-prototype/README.md . 2>/dev/null | sed -n '1,120p'

Repository: NX1X/OpenTaxMap

Length of output: 4946


Clarify the range-request bandwidth statement.

Range requests avoid downloading the complete PMTiles archive. Clients still load metadata, indexes, and tile bytes for the tiles they view. Replace “not user bandwidth” with wording such as “does not require downloading the full archive.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/maplibre-prototype/README.md` around lines 24 - 27, Update the
bandwidth statement in the README’s PMTiles hosting explanation to clarify that
range requests avoid downloading the full archive, while clients still transfer
metadata, indexes, and viewed tile bytes; replace the inaccurate claim that file
size does not affect user bandwidth.

Binary file added design/maplibre-prototype/preview-english.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added design/maplibre-prototype/preview-hebrew.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions design/maplibre-prototype/prototype.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!doctype html><html><head><meta charset="utf-8">
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet">
<style>html,body,#map{margin:0;height:100%;width:100%}</style></head>
<body><div id="map"></div>
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
<script src="https://unpkg.com/pmtiles@3.0.6/dist/pmtiles.js"></script>
<script>
maplibregl.setRTLTextPlugin('https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js', true);
const proto = new pmtiles.Protocol();
maplibregl.addProtocol('pmtiles', proto.tile);
const LANG = new URLSearchParams(location.search).get('lang') || 'he';
const nameField = ['coalesce', ['get', 'name:'+LANG], ['get','name']];
const style = {
version: 8,
glyphs: 'https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf',
sources: { pm: { type:'vector', url:'pmtiles://http://localhost:4401/israel.pmtiles', attribution:'OpenStreetMap' } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '/design\/maplibre-prototype\/prototype.html/p;/scripts\/build-tiles.sh/p'

echo
echo "== prototype relevant lines =="
if [ -f design/maplibre-prototype/prototype.html ]; then
  nl -ba design/maplibre-prototype/prototype.html | sed -n '1,80p'
fi

echo
echo "== build-tiles attribution references =="
if [ -f scripts/build-tiles.sh ]; then
  nl -ba scripts/build-tiles.sh | sed -n '1,120p'
fi

echo
echo "== local references to OpenStreetMap attribution text =="
rg -n "OpenStreetMap|attribution|attributionControl|contributors" .

Repository: NX1X/OpenTaxMap

Length of output: 302


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== prototype relevant lines =="
if [ -f design/maplibre-prototype/prototype.html ]; then
  awk '{ printf "%6d\t%s\n", NR, $0 }' design/maplibre-prototype/prototype.html | sed -n '1,80p'
fi

echo
echo "== build-tiles attribution references =="
if [ -f scripts/build-tiles.sh ]; then
  awk '{ printf "%6d\t%s\n", NR, $0 }$1' scripts/build-tiles.sh | sed -n '1,120p'
fi

echo
echo "== local references to OpenStreetMap attribution text =="
rg -n "OpenStreetMap|attribution|attributionControl|contributors" .

Repository: NX1X/OpenTaxMap

Length of output: 8396


🌐 Web query:

MapLibre GL JS add control attribution control disabled source attribution only

💡 Result:

To disable the default MapLibre attribution (which typically includes the "MapLibre" text) and show only source-specific attributions, you cannot simply configure the AttributionControl directly, as it is designed to manage all attributions collectively [1][2]. Instead, you should override the default behavior by controlling the attribution at the map level and providing your own custom configuration [3]. Here is the recommended approach to achieve source-only attribution: 1. Disable the default attribution control when initializing the map: Set attributionControl: false in your maplibregl.Map constructor options [4][3]. 2. Add a custom AttributionControl: After the map is initialized, add a new AttributionControl instance with a customAttribution option set to an empty string (or only the attributions you wish to display). By setting customAttribution: "" (or omitting the default "MapLibre" string), you can effectively clear the default text [1][5]. Example: const map = new maplibregl.Map({ container: 'map', style: 'style.json', attributionControl: false // Disables the default attribution control }); map.addControl(new maplibregl.AttributionControl({ customAttribution: '' // Set to empty or your specific custom string to exclude "MapLibre" })); Note that the AttributionControl automatically collects attribution information defined in your map style's sources (e.g., the attribution property defined within each source in your style JSON) [6][2]. By using the method above, the control will still display the source-specific attributions while allowing you to omit the default library credit [2].

Citations:


🌐 Web query:

MapLibre GL JS v4 attributionControl false source attribution does not show

💡 Result:

In MapLibre GL JS v4, setting attributionControl: false in the Map constructor completely disables the default AttributionControl UI element [1][2]. When this control is disabled, the map no longer renders any attribution information in the UI [1][3]. The reason the attribution stops showing is that the AttributionControl class is responsible for collecting and displaying attribution strings defined in your sources [4][5]. If you disable this control, you are responsible for displaying that information manually [6]. To display source attribution while hiding the default control, you must manually extract the attribution data and present it in your own UI [6]: 1. Disable the default control: new Map({ attributionControl: false }) [2][3]. 2. Access the source attribution strings from your map style's source definitions (e.g., map.getStyle().sources['your-source-id'].attribution) [6]. 3. Manually render these strings in your custom UI component or HTML element [6]. Note that many map data providers (such as OpenStreetMap) legally require attribution to be visible [7][8]. Simply setting attribution: false inside a source definition is often invalid and may trigger schema validation errors [6]. If you need to customize the look or behavior of the attribution, it is generally recommended to use the AttributionControl with custom options or CSS rather than disabling it entirely [2][8].

Citations:


Display the required OpenStreetMap attribution.

attributionControl:false suppresses MapLibre’s attribution UI, so the source attribution:'OpenStreetMap' is not shown to users. Re-enable the control and update the source attribution to the required (c) OpenStreetMap contributors, or render the attribution manually.

[compliance_and_legal]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/maplibre-prototype/prototype.html` at line 16, Update the MapLibre map
configuration to re-enable the attribution control and change the
`sources.pm.attribution` value to the required “(c) OpenStreetMap contributors”
text, ensuring it is displayed to users.

layers: [
{ id:'bg', type:'background', paint:{'background-color':'#f6f4ee'} },
{ id:'earth', source:'pm','source-layer':'earth', type:'fill', paint:{'fill-color':'#f2efe6'} },
{ id:'landcover', source:'pm','source-layer':'landcover', type:'fill', paint:{'fill-color':'#e6ecd8','fill-opacity':0.6} },
{ id:'water', source:'pm','source-layer':'water', type:'fill', paint:{'fill-color':'#a7c7e7'} },
{ id:'roads-minor', source:'pm','source-layer':'roads', type:'line', filter:['!=',['get','kind'],'highway'], paint:{'line-color':'#ffffff','line-width':['interpolate',['linear'],['zoom'],8,0.4,14,2]} },
{ id:'roads-major', source:'pm','source-layer':'roads', type:'line', filter:['==',['get','kind'],'highway'], paint:{'line-color':'#f6d488','line-width':['interpolate',['linear'],['zoom'],6,0.6,14,4]} },
{ id:'places', source:'pm','source-layer':'places', type:'symbol',
filter:['in',['get','kind'],['literal',['city','town','village','locality','suburb']]],
layout:{'text-field':nameField,'text-font':['Noto Sans Regular'],'text-size':['interpolate',['linear'],['zoom'],7,10,12,15],'text-max-width':7},
paint:{'text-color':'#333','text-halo-color':'#fff','text-halo-width':1.4} },
],
};
const map = new maplibregl.Map({ container:'map', style, center:[35.0,31.6], zoom:7, attributionControl:false });
map.on('load', ()=>{ window.__ready=true; });
map.on('error', e=>{ (window.__errs=window.__errs||[]).push(String(e.error||e)); });
</script></body></html>
33 changes: 33 additions & 0 deletions scripts/build-tiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Build the self-hosted Israel basemap tiles for the v0.3.0 MapLibre map.
#
# Extracts an Israel + West Bank bounding box from the Protomaps global daily
# build (over HTTP range requests, so it does not download the planet) into a
# single .pmtiles archive. That file is then uploaded to Cloudflare R2 and
# served first-party by the Worker (see wrangler.jsonc R2 binding).
#
# Requirements: the `pmtiles` CLI (https://github.com/protomaps/go-pmtiles).
# Network access to demo-bucket.protomaps.com.
#
# Usage: scripts/build-tiles.sh [maxzoom] (default maxzoom 14)
set -euo pipefail

MAXZOOM="${1:-14}"
# Israel + West Bank + a margin (matches DATA_BOUNDS in the app).
BBOX="34.2,29.4,35.95,33.45"
SRC="https://demo-bucket.protomaps.com/v4.pmtiles"
OUT="tiles/israel.pmtiles"

mkdir -p tiles
echo "Extracting bbox $BBOX at maxzoom $MAXZOOM from $SRC ..."
pmtiles extract "$SRC" "$OUT" --bbox="$BBOX" --maxzoom="$MAXZOOM"

echo
echo "Built $OUT ($(du -h "$OUT" | cut -f1))."
echo "Layers: $(pmtiles show --metadata "$OUT" | python3 -c 'import sys,json;print(",".join(l["id"] for l in json.load(sys.stdin)["vector_layers"]))')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Declare the python3 prerequisite.

The script uses python3 to parse PMTiles metadata, but the requirements list does not mention Python 3. With set -euo pipefail, extraction can succeed and the script can still fail during reporting. Add Python 3 to the requirements or replace this parser with a guaranteed dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/build-tiles.sh` at line 27, Declare Python 3 as a prerequisite for
scripts/build-tiles.sh because its reporting pipeline invokes python3 to parse
PMTiles metadata. Update the script’s existing requirements
declaration/documentation to include Python 3, preserving the current metadata
extraction command.

echo
echo "Next: upload to R2, e.g."
echo " npx wrangler r2 object put opentaxmap-tiles/israel.pmtiles --file=$OUT --remote"
echo
echo "The Protomaps basemap is derived from OpenStreetMap and Natural Earth."
echo "Attribution '(c) OpenStreetMap contributors' is required in the map UI."
Loading