feat: MapLibre GL + PMTiles frontend rendering#140
Merged
Conversation
…entation - Change --profile=raster to --profile=mercator in gdal2tiles so tiles are proper Web Mercator tiles that MapLibre can render at correct scale - Add EPSG:4326 georeferencing to VRT (places image at equator matching armaToLatLng coordinate conversion) - Remove DstYOff flip — satellite tile Y maps directly to image row, GeoTransform handles orientation (south-up raster, GDAL reprojects) - Remove MBTiles y-flip — mercator profile outputs TMS convention, same as MBTiles (no conversion needed) - Calculate zoom levels dynamically via MercatorZoomForWorld instead of hardcoded maxZoom=6 (Altis: z10-17, Stratis: z12-16) - Use math.Round instead of math.Ceil for maxZoom to avoid oversampling (Altis: ~14K tiles/~350MB vs ~54K tiles/~1.3GB) - Add -r average resampling to reduce edge artifacts - Add minzoom/maxzoom to style.json source metadata
Arma satellite tiles have 32px overlap on all edges for in-engine texture blending. Crop to 480px effective size in the VRT SrcRect/DstRect to eliminate visible seam duplication in PMTiles output. Also upscale undersized 4×4 ocean placeholder tiles to 512×512 via nearest-neighbor before PNG encoding, so all tiles match the VRT layout. Other fixes: - Remove TMS Y-flip in MBTiles (gdal2tiles mercator already uses TMS) - Add georeferenced EPSG:4326 GeoTransform to VRT (north-up, equator) - Compute min/max zoom from world size via MercatorZoomForWorld() - Pass MinZoom/MaxZoom through Job and into map.json/style.json
…gery The satellite tile grid is sparse — Altis has 1863 tiles spanning X=2-60, Y=8-55, not the full 64×64 grid. Using (maxTile+1)*tileEffective as VRT dimensions stretched the image to fill worldSize, causing markers to misalign: 3× vertical error, 1× horizontal (14.3% vs 4.9% stretch). Fix: set VRT canvas to worldSize×worldSize (1 pixel = 1 meter). Tiles are placed at their correct pixel positions; missing ocean tiles are transparent.
Parse OPRW v24/25 WRP files to extract elevation, building objects, and road networks, then generate vector.pmtiles via tippecanoe. The pipeline produces contour lines (marching squares), building point features, and road polylines as separate vector tile layers. Key implementation details: - Full WRP binary parser with QuadTree, LZO decompression, and section navigation using LZO end-of-stream markers to handle go-lzo's bufio overread - Contour generation via marching squares algorithm at configurable intervals (10m minor, 50m major) - Style.json updated with vector layers when vector.pmtiles exists - Vector tile stage is optional (skips gracefully if tippecanoe missing or WRP parsing fails)
… extent topo.pmtiles had world-spanning bounds (-180,-85 to 180,85) because TilesToMBTiles only wrote name/format/type metadata. pmtiles convert falls back to global bounds when these fields are missing. Now writes bounds, minzoom, maxzoom, and center derived from worldSize.
Surface roads in Arma WRP files are terrain textures, not geometry. The actual road polylines live in roads.shp inside the map's _data.pbo. - Add shapefile reader (.shp + .dbf) and RoadsLib.cfg parser - Auto-detect coordinate offset from shapefile bounds vs worldSize - Extract 1414 road polylines from shapefile + 40 bridges from RoadNet - Style roads by type (main road, road, track) with varying width/color - Fall back to RoadNet-only extraction when data PBO is unavailable
# Conflicts: # internal/maptool/mbtiles.go # internal/maptool/metadata.go # internal/maptool/vector.go # internal/maptool/wrp.go
# Conflicts: # internal/maptool/mbtiles.go # internal/maptool/metadata.go # internal/maptool/tiles.go # internal/maptool/vector.go # internal/maptool/wrp.go
Adds L.Control.MaplibreStyles for switching between the three generated style variants at runtime via MapLibre GL's setStyle() API.
Probe satellite.json and hybrid.json with HEAD requests on init, hide buttons for unavailable styles. Hide entire control if only one variant exists.
Saves the user's style choice (topo/satellite/hybrid) and restores it on next page load. Falls back to topo if localStorage unavailable or saved index is out of range.
HEAD returns 405 on the Go server — switch to GET for variant probing. Resolve saved style preference before creating the MapLibre layer so the correct style loads on first render with no flash or double-load.
Avoid calling map.project() with undefined mapMaxNativeZoom in MapLibre mode. Output Arma coordinates instead.
The Go server only registered GET for map assets, returning 405 for HEAD requests. Register HEAD route so the style switcher can probe variant availability without downloading the full response body.
Merging this branch will not change overall coverage
Coverage by fileChanged files (no unit tests)
Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
maplibreStylefield in map.jsonarmaToLatLng()gate function — works transparently across both modes/images/maps/*on the servermaplibreStyleis absentChanged files
static/scripts/ocap.js— dual-mode CRS, MapLibre layer init, saved style resolutionstatic/leaflet/L.Control.MaplibreStyles.js— new style switcher controlstatic/leaflet/L.Control.MaplibreStyles.css— styling for the controlstatic/leaflet/L.Layer.Grid.js— MapLibre-mode coordinate conversion and zoom thresholdsstatic/index.html— CDN includes for MapLibre GL, PMTiles, leaflet adapterinternal/server/handler.go— HEAD route for map assetsTest plan