Atlaslog
Field notes · 120 cases

Every GIS failure has a symptom.
Start from the symptom.

Blank maps, coordinate offsets, 404 tiles, floating models, cameras under the terrain. Each note gives the real cause, an ordered checklist and a fix you can copy.

Coordinates & projections

20 notes
001Coordinates shift hundreds of meters on Chinese basemapsGPS points land a few hundred meters from where they were recorded once drawn on Amap, Tencent or Baidu tiles. The shift is a systematic WGS84 to GCJ-02 (or BD-09) datum difference, not bad data: it ranges from about 265 m in Urumqi to 622 m in Guangzhou and changes direction with location.Read → 002Basemap and data are each correct but misalign when overlaidData on a blank canvas sits on the right roads and the basemap alone looks normal, yet the overlay is uniformly shifted. The cause is a CRS mismatch between the two sources, a doubled conversion, or Baidu's non-standard tile numbering.Read → 003Same points converted twice: offset grows from 555 m to 1110 mThe backend already stores GCJ-02 but records no CRS tag, so the frontend converts again. GCJ-02 encryption is not idempotent: f(f(x)) keeps adding the same north-east increment instead of undoing it.Read → 004Fixed 0.0065 subtraction from BD-09 still leaves 481 mSubtracting 0.0065 and 0.006 from a Baidu coordinate only brings it back to GCJ-02, so 481.8 m to WGS84 remains. The residual is not constant: it becomes 606.6 m in Shenzhen and 621.7 m in Guangzhou.Read → 0053857 metre values passed as lat/lng make markers vanishThe API returns Web Mercator metres such as 13522390, 3669872, but the code passes them to L.marker as latitude and longitude. The view jumps toward the Arctic, the marker is off-screen, and nothing is logged.Read → 006UTM zone off by one shifts Xi'an 551 km eastThe data was projected in UTM zone 49 but the layer declares zone 50. Each zone is 6 degrees of longitude apart, which at 34 degrees north equals 551 km, while the northing stays correct.Read → 007Gauss-Kruger zone number mistaken for central meridian, layer off 275 kmCGCS2000 3-degree Gauss-Kruger zones are keyed by central meridian, so EPSG:4546 is 108 E and EPSG:4547 is 111 E. Choosing 4547 for Shaanxi data moves it 2.9 degrees, about 275 km, east.Read → 008GeoJSON without a CRS: 3857 metres read as degreesA GeoJSON exported straight from EPSG:3857 geometry holds metre coordinates but no usable CRS declaration. RFC 7946 removed the crs member and every parser assumes degrees, so nothing renders.Read → 009CGCS2000 and WGS84 mixed: 0.4 m error at large scaleCGCS2000 and WGS84 share an almost identical ellipsoid, so tutorials call them interchangeable. Epoch drift of about 3 cm/year in China accumulates to roughly 0.4 m by 2026, which only fails at 1:500 scale.Read → 010EPSG:4479 used as geographic data: coordinates reach seven digitsEPSG:4479 is CGCS2000 geocentric Cartesian, returning X/Y/Z in metres rather than degrees. Passing those values to Cesium.Cartesian3.fromDegrees sends the camera off the globe and leaves a black screen.Read → 011Changing CRS to EPSG:4326 squashes the map verticallyL.CRS.EPSG4326 gives one degree of longitude and one degree of latitude the same pixel length, but a degree of longitude shrinks by cos(lat). China is therefore flattened, circles become ellipses, and the distortion grows with latitude.Read → 012Two layers on different CRSs overlap half a screen apartAn ImageStatic overlay receives an extent in degrees while the View is EPSG:3857, so 116.0 is read as 116 metres and the image collapses into the corner. Converting the extent to metres fixes position but ImageStatic still cannot reproject, so the image is squashed.Read → 013proj4 throws unknown projection: EPSG:4547proj4js ships a built-in table with 4326, 3857 and 326xx but not the CGCS2000 3-degree codes, so an unregistered code makes its lookup return undefined and throws. Some builds alias 4490 to 4326, which appears to work but lacks ellps=GRS80.Read → 014WMS 1.3.0 BBOX is lat-first, so the layer is offsetWMS 1.3.0 with CRS=EPSG:4326 requires BBOX in latitude,longitude order, while 1.1.1 with SRS always used longitude,latitude. A lon-lat BBOX sent to 1.3.0 is read as latitudes beyond plus/minus 90 and returns a blank or flipped image.Read → 015Seven-parameter scale not divided by 1e6 shifts 20 km points by 24 kmThe Bursa-Wolf scale factor is in ppm and must be multiplied by 1e-6. Entering -1.2 instead of -1.2e-6 scales distances by about 1.2, and the error grows linearly from the origin.Read → 016Foreign coordinates encrypted too: 600 m cliff at the Shenzhen RiverA GCJ-02 implementation without the outOfChina guard encrypts coordinates anywhere on Earth, so the mainland side of a border moves while the Hong Kong side does not. Tokyo shifts more than 400 m even though Japan is outside the GCJ-02 area.Read → 017Baidu Mercator without MCBAND segments puts points 3 km offBaidu's BD09MC is not EPSG:3857; its Y axis uses an 18-segment MCBAND/LLBAND lookup table. Computing tile coordinates with the standard Mercator formula shifts the mosaic about 3.8 km south-west, which is 15 tiles at zoom 18.Read → 018Two-decimal coordinates snap the heatmap 1.1 km off gridA decimal(9,2) column quantises coordinates to a 0.01 degree grid, about 1.11 km in latitude and 0.85 km in longitude at 40 N. Hundreds of POIs collapse into one cell, producing a blocky heatmap displaced from the road network.Read → 019Latitude 89 in Web Mercator yields Infinity and NaN tile indicesWeb Mercator y = R*ln(tan(PI/4 + phi/2)) diverges at the poles, and EPSG:3857 is clipped at plus/minus 85.0511287798. Beyond that y exceeds 20037508 or becomes Infinity, so tile indices become NaN and the layer silently stops rendering.Read → 020Southern UTM without the 10,000,000 m false northing gives negative yEPSG:32756 carries a false northing of 10000000 m so the equator maps to y=10000000. A +proj=utm +zone=56 definition without +south treats Sydney as northern and returns y=-3748000, exactly 10 million metres away.Read →

Loading & display

15 notes
021Map renders blank with no console errorNothing renders while the console stays clean and tiles may even return 200. The usual causes are a container with zero height at init, a library that never loaded, an init that ran while hidden, or a higher z-index element covering the canvas.Read → 022Map misaligned inside a modal, drawer or tabA map initialised while display: none gives the library a 0x0 container, so only the top-left corner is drawn later. Dragging or zooming sometimes repairs it because those gestures force an internal size recalculation.Read → 023Tiles return 404 or are blocked by CORSThe basemap shows grey squares because requests fail: 404 from a bad URL template or a swapped y-axis, 403 from an invalid key or referer, or CORS when crossOrigin is enabled for canvas export.Read → 02420 MB GeoJSON parsed synchronously freezes the main thread for 8 sfetch takes only 600 ms, but L.geoJSON creates 120,000 path objects, computes bounds and binds events, locking the page for about 8 s. JSON.parse is not the bottleneck; per-feature vector object creation is.Read → 025Shapefile attribute text garbled while geometry is perfectThe DBF stores no encoding, so GDAL checks .cpg, then ENCODING in .prj, then falls back to the system code page. UTF-8 bytes read as GBK produce strings of rare CJK glyphs, while GBK read as UTF-8 produces question marks.Read → 026CSV longitude and latitude columns swapped, points land off SomaliaThe CSV header was name,lat,lng but the code read by column index, so 116.4 became a latitude and 39.9 became a longitude. Longitudes above 90 are silently dropped by Leaflet, which is why the points vanish instead of appearing in the Indian Ocean.Read → 027MultiPolygon missing one array level reports invalid geometryGeoJSON requires four nesting levels for MultiPolygon, three for Polygon. A backend that retypes a single Polygon to MultiPolygon produces only three levels, so turf throws and QGIS refuses to parse the file.Read → 028WMTS TileMatrix starts at 0, so every tile is one level offGeoServer names its WMTS levels EPSG:4326:0 through EPSG:4326:21, but level 0 is 1:559082264 and covers the globe with a 2x1 grid, not the single tile that XYZ expects. The client is therefore always one zoom level out.Read → 029GeoServer declares 4326 data as 4490, layer shifts half a meterThe data is WGS84 but the layer's nativeCRS is set to EPSG:4490, so GeoServer treats them as different datums and applies a shift on every request. The boundary moves about 0.45 m west and fails a 1:1000 acceptance check.Read → 030Null geometry feature makes fitBounds throw null is not an objectGeoJSON allows geometry: null for features with attributes but no location. Leaflet turns those into empty layers without _bounds, and fitBounds throws while extending over them, aborting the rest of the init chain.Read → 031TopoJSON loses transform and coordinates become integers like 7823TopoJSON quantizes coordinates to integers and stores the restore parameters in a top-level transform block. When a processing script strips that block, topojson.feature returns raw quantized integers and the layer renders empty or collapses near [0,0].Read → 032Polygon crossing the antimeridian renders as a band across the whole globeA Fiji EEZ polygon lists longitudes from 177.3 down to -178.2. GeoJSON has no wraparound, so the renderer joins 179.5 straight to -179.5 across the map, and turf.area reports 4.9e14 square metres instead of about 1.1e12.Read → 033Shapefile without .prj read as lat/lon and lands 500 km east of Xi'anOnly .shp, .shx and .dbf were delivered, and the .shp header stores no coordinate system. GDAL falls back to EPSG:4326, so metre values around 500000 become 500000 degrees and the polygons appear near the West African coast.Read → 034DBF field names truncated at 10 characters and properties read as undefinedDBF from dBase III caps field names at 10 bytes, so population_total exports as population and district_name as district_n. Names that collide overwrite each other, and the frontend reading feature.properties.population_total gets undefined and a NaN legend.Read → 035KML icons use relative paths and load as broken imagesKML resolves a relative href against the URL of the KML file itself, but parsers such as omnivore join it against the page URL. A KML at /data/poi.kml with href images/pin.png requests /cookbook/images/pin.png and returns 404.Read →

Performance

2 notes

Services & tile access

10 notes
038Switching to a domestic basemap: what actually has to changeMoving from OpenStreetMap to Tianditu, Amap or Tencent is not just a URL swap. The coordinate system, key, protocol and quota rules all change, and Amap and Tencent use GCJ-02, so business data must be converted before display.Read → 0393D Tiles never appear although tileset.json returns 200The promise resolves and nothing is thrown, yet the scene stays empty. The usual causes are a missing transform in tileset.json, an oversized geometricError, a camera aimed elsewhere, or missing CORS and MIME types for the binary tiles.Read → 040WMS keeps returning 502 while the service is still aliveGeoServer on port 8080 answers fine directly, but through Nginx a 2048x2048 GetMap returns 502 Bad Gateway and occasionally succeeds. Rendering that tile takes tens of seconds, beyond the default 60-second upstream read timeout.Read → 041CORS preflight rejected: GET passes but POST does notTile requests work but submitting an analysis job fails. Because of Content-Type: application/json or an Authorization header the browser sends an OPTIONS preflight, and the backend answers 405 with Allow listing only GET, HEAD and POST.Read → 042Data updated but tiles still show last weekGetMap returns the new roads while WMTS serves the old image, sometimes with a visible seam between new and old tiles at one zoom level. Tiles are keyed on layer, style, grid set, level, row and column, so changing only the data invalidates nothing.Read → 043WMS query parameters disappear behind the proxyThe browser sends a complete URL but GeoServer answers that the LAYERS parameter is missing. Nginx logs the original $request intact, so the loss happens during forwarding, where proxy_pass with a URI replaces both path and query string.Read → 044SLD uploaded but the map still uses the default greyThe SLD previews correctly inside GeoServer, yet GetMap returns the default grey fill. The log says the requested style is unavailable and falls back, usually because the style name lacks the workspace prefix or is not set as the layer default.Read → 045GetCapabilities takes a minute and clients time outGetCapabilities returns in 40 to 60 seconds with an 18 MB document, while a single GetMap takes 200 ms. With 1200 or more layers each publishing a full SRS list, metadata assembly is the bottleneck rather than rendering.Read → 046HTTPS page loads HTTP tiles and the browser blocks themAfter the site moved to HTTPS the basemap requests tiles over http:// and Chrome blocks them as mixed content. Either the protocol is hardcoded in the frontend or only the main site has a certificate.Read → 047Connection pool exhausted and every WMS returns 503At the 9 a.m. peak all layers slow down and then the whole site returns 503 until GeoServer is restarted. The DBCP pool times out waiting for connections while PostgreSQL connections sit at max_connections.Read →

Interaction

1 notes

3D & Cesium

9 notes
049Cesium returns 401 because the ion token never took effectThe globe renders but imagery is black and every api.cesium.com request returns 401. Cesium reads Ion.defaultAccessToken once when the Viewer or CesiumWidget is constructed, so assigning it after new Viewer leaves the default resources using an old or undefined token.Read → 0503D Tiles render as a flat white mass with no edgestileset.json and all tiles return 200 and zoomTo shows the building volumes, but every face is nearly pure white. Untextured b3dm blocks without vertex normals receive the default up-facing normal, so all faces get near-1.0 lighting and merge with a white basemap.Read → 051Model floats above or sinks into the terrainThe same model.glb sits correctly without terrain but hovers about 8 metres above once world terrain is enabled, and in mountains half of it is buried. Three factors stack: the model origin is not at its base, the height is hardcoded to 0, and terrain exaggeration scales the ground.Read → 052Camera goes below ground and the screen turns blackHolding right-drag upward tilts the view past the horizon until camera height becomes negative, logged as -1840.5 metres, and the frustum points at the geoid. Collision detection or its settings have been disabled.Read → 053Terrain tiles load slowly and the first screen spins for over ten secondsImagery fills in immediately but terrain shows jagged holes while the queue holds 231 pending requests against only 6 active. The bottleneck is the request cap plus extra water mask and normal fetches, not bandwidth.Read → 054Frame rate drops to 18 with two thousand entities500 stations stay smooth, while 2000 drag at 18 to 22 FPS. Entity properties are re-evaluated every frame, over 20000 evaluations at that count, and each Label rebuilds its own texture atlas.Read → 055CesiumWidget has zero size and the globe is never createdThe container declares 100% width and height but measures 0 by 0, so Cesium throws during construction and the canvas stays at width 0. Percentage heights need a chain of definite heights, and a display none ancestor defeats them entirely.Read → 056toDataURL returns a black image, Cesium includedThe scene looks right on screen but the exported PNG is black or fully transparent. Either the WebGL drawing buffer was already cleared because preserveDrawingBuffer is off, or cross-origin imagery tainted the canvas.Read → 057Bloom washes the whole scene out to whiteEnabling bloom makes buildings, water and bright labels glow outward and lifts overall brightness until dark detail disappears, with a visible frame cost. The default delta of 1.0 sends every pixel through the bloom pass.Read →

three.js rendering

8 notes
058Model loads completely black with no visible materialGLTFLoader reports success and the progress bar reaches 100%, but the model is a black silhouette that does not change when rotated. MeshStandardMaterial needs light, and the scene either has no light sources or only a dim ambient.Read → 059Texture is upside down and text reads backwardsThe same ground-sign texture loads correctly through TextureLoader but appears flipped when generated from canvas or binary data. three.js defaults flipY per source: true for image and canvas textures, false for DataTexture and glTF-embedded textures.Read → 060Model lies on its side and does not match map orientationModels built Z-up, the GIS and CAD convention, arrive lying flat because three.js and glTF are Y-up, so rotation.y then turns the wrong axis. Longitude and latitude used directly as x and y also place the model outside the scene.Read → 061Two coplanar surfaces flicker and get worse with distanceA road surface laid on the ground mesh shimmers in stripes and speckles because both meshes occupy nearly the same plane and the depth buffer cannot rank them. Stripe width grows with camera distance and with an oversized near/far range.Read → 062Stacked transparent glass renders as solid slabsWith transparent: true and opacity 0.4 the glass panes still occlude everything behind them, so the interior or the next facade never shows through. Transparent meshes keep depthWrite enabled and are sorted per object, not per face.Read → 063Twenty million points freeze the tab at 3 fpsA 1.8 GB PLY loads as one Points object, pushing tab memory to 3.1 GB and the frame rate to 3 fps. All vertices go through a single draw call, saturating the vertex stage and fill rate.Read → 064Imported glTF is thumbnail-sized or swallows the cameraTwo glTF exports from different tools arrive at wildly different scales because the exporter ignored the metre unit, so one building is 1.87 m tall and another 187000 units. A parent scale that is already non-1 multiplies the error.Read → 065InstancedMesh trees are randomly rotated and half vanishThe 5000 instances render, but orientation and size ignore the per-instance values, and whole stands disappear when the camera moves. The instance matrices were never flagged for upload and the bounding sphere still covers only the base geometry.Read →

deck.gl visualization

6 notes
066setProps with new data leaves the layer unchangedThe filter handler builds a new layer and calls deck.setProps, the array in the console is shorter, but the map still shows every point in the old colour. deck.gl shallow-compares layer id and props, so a mutated array or a reused instance counts as no change.Read → 067Clicks always return info.object nullThe click handler fires but receives color null, index -1 and object null, because the layer was never marked pickable or a full-screen layer above it wins the pick. Picking is a separate render pass that only opted-in layers join.Read → 0681.2 million GPS rows crash the tab with an array buffer errorParsing 1.2 million CSV rows into object arrays costs 400 MB before any rendering, and deck.gl keeps the previous copy for diffing, so the peak approaches 2.4 GB. The page dies inside TripsLayer.initializeState with a Float32Array allocation failure.Read → 069deck.gl layers drift behind the MapLibre basemapWhen deck.gl and MapLibre each own a camera and viewState is pushed on the move event, the layer tracks a frame late and the offset grows with zoom. A transformed or padded container adds a fixed pixel offset on top.Read → 070Swapped lat/lng order sends every point to the AntarcticThe accessor returns [d.lat, d.lng] while deck.gl expects [lng, lat], so 39.9 becomes a longitude and 116.4 an out-of-range latitude. Mixing 2- and 3-element points in getPath also breaks the buffer length check.Read → 071Duplicate layer ids make one legend toggle hide both layersTwo layers share the id 'points', so the diff treats the second as an update of the first and only one layer exists. Toggling one legend entry then changes the other, and getLayers() returns a single record.Read →

MapLibre styles & layers

5 notes

Leaflet containers & layers

5 notes

OpenLayers layers

5 notes

Build & deployment

7 notes
087Hashed JS and CSS 404 after deploy and the page goes whiteThe build emits content-hashed filenames, and either only index.html was uploaded or the base path is wrong, so every /assets/index-8f3a2b1c.js request misses. A cached old HTML that references deleted hashes produces the same white page.Read → 088Sub-directory deploy breaks routes, icons and API callsThe app is served from /gis/ but the build base, router history base and hard-coded fetch paths all assume the root, so assets, icons and API calls request the wrong prefix. Direct navigation to /gis/map returns 404 because the server has no such route.Read → 089Two Leaflet copies on one page break map.remove()The host page loads Leaflet 1.7.1 from a CDN while the embedded app bundles 1.9.4, and both assign to window.L, so the later copy wins. Layers created by the other copy fail instanceof checks inside map.remove().Read → 090Moving aggregation into a Worker fails with document is not definedThe Worker entry imports a module chain that touches window or document at module scope, so it dies before any message arrives. Map libraries probe for window or create a canvas merely by being imported.Read → 091Importing a CommonJS package breaks the production buildThe dev server runs fine, but `npm run build` aborts with a SyntaxError saying the module provides no export named 'default'. The package is UMD/CJS only, so the bundler's ESM interop and dependency pre-bundling decide whether a default export exists.Read → 092Three copies of three.js on the page break all raycast pickingModels render, but clicking never hits anything and drag controls stop working. The console warns that multiple instances of Three.js are being imported: a plugin and a GIS widget each bundle their own copy, so `instanceof` checks across instances fail.Read → 093White screen for some users after a release: ChunkLoadErrorTen minutes after a deploy, some users see a blank page while others load normally, and clearing the cache fixes it. The cached `index.html` still points at a hashed chunk that was deleted on the origin, so the lazy chunk 404s on the CDN.Read →

Security & compliance

2 notes

Tiles

12 notes
096TMS tiles render upside down because the Y axis counts from the bottomEvery tile loads and the mosaic is seamless, but the whole map is flipped vertically: at z18 Beijing lands in the southern hemisphere while east-west orientation is correct. The directory uses TMS numbering while the client requests XYZ.Read → 097Tile directories start at level 1, so the map renders one level too largeThe offline package exported by MOBAC contains levels 1 through 19 and no level 0, while the client is configured to start at zoom 0. Every request is one level off, so the basemap looks magnified and the top level returns blanks.Read → 098Tianditu tiles return 403 because the Referer is not whitelistedPasting the WMTS URL into the address bar displays the tile, but inside the map every request returns 403 with 'Invalid referer'. The key is valid; the browser's Referer (localhost or file://) is simply not in the console whitelist.Read → 099Exporting the map to an image throws 'canvas tainted'The map displays correctly but the export button throws a SecurityError, or html2canvas returns a blank basemap area. The tiles were drawn without `crossOrigin`, or the tile server sends no `Access-Control-Allow-Origin` header.Read → 100Tiles return 204 instead of 404, leaving invisible white gapsThe tile service answers `204 No Content` outside its data extent, which the browser treats as a successful load. The area renders as transparent white with no grey block and no `tileerror`, so monitoring reports everything healthy.Read → 101One-pixel white seams between tilesEvery tile loads, yet the mosaic shows thin white lines that get denser at lower zoom. Fractional pixel positioning from a container size that is not a multiple of `tileSize`, together with browser scaling and antialiasing, produces the gaps.Read → 102Zoom jumps from 17 straight to 19, skipping two levelsOne wheel notch advances two zoom levels, and those skipped levels never render. `zoomDelta` was set to 2 while `zoomSnap` is 0.5, so every gesture moves two levels and the intermediate tile requests never happen.Read → 103Seven tile subdomains configured, still only six connections`subdomains: '01234567'` is set, but the network panel still shows six active requests with everything else queued. Either the URL template lacks `{s}`, or all subdomains resolve to one host behind HTTP/1.1 where the limit still applies.Read → 104512-pixel tiles laid out as 256: blurry basemap and shifted featuresAfter switching to 512x512 tiles the basemap looks softer and vectors are shifted by roughly half a tile, worsening toward the map edge. `tileSize` stayed at 256, so Leaflet compressed each tile and halved its coverage.Read → 105Baidu tile numbering is offset from the XYZ gridComputing Baidu tile numbers with Google's XYZ formula gives x=215971, y=99264 at z18 for Tiananmen, while the real tile is x=209317, y=96843. Baidu numbering is based on BD09MC with its own origin and per-level shift, so every row of a stitched image is displaced.Read → 106Tiles cached for 30 days keep showing the old stylingThe service re-seeded tiles with a new SLD, but users still see the old basemap days later. `Cache-Control: max-age=2592000` on an unversioned URL means browsers never revalidate and keep reading the disk copy.Read → 107Map repeats three times at zoom 3 because noWrap is offAt zoom 3 in a 1600 px container, three copies of China appear side by side and the outer copies are discontinuous. Leaflet wraps the world horizontally whenever 2^z multiplied by `tileSize` is narrower than the viewport.Read →

Styling & rendering

13 notes
108A style expression of the wrong type hides the entire layerThe layer and its legend are present but no feature is drawn, and the console shows a single warning. A property name was written as a bare string instead of `["get", ...]`, or `fill-opacity` received a string, so type validation rejects the paint and the layer draws nothing.Read → 109Hole ring winds the wrong way and the lake gets filledA polygon with a 240-vertex outer boundary and an inner lake ring renders with the lake painted in the same color. Both rings wind clockwise, so nonzero fill never cancels the hole and the satellite basemap disappears underneath.Read → 110Labels pile up, and text-allow-overlap makes it worseOnly a few dozen of 823 POI labels appear because collision detection hides the rest; enabling `text-allow-overlap` shows all 823 on top of each other. That flag disables layout rather than solving crowding.Read → 111Long place names are truncated by a small text-max-widthA district label shows only its first six characters with no ellipsis and no wrapping. `text-max-width: 4` is measured in ems, so only about four Han characters fit per line and anything beyond the computed lines is cut off.Read → 112Polygon layer covers the road lines, and zIndex makes it worseAreas are added before roads yet render on top of them, and setting `{ zIndex: 999 }` on the line layer makes the lines vanish. Leaflet's `zIndex` is a renderer and pane property, not a per-layer ordering control inside one renderer.Read → 113Two 50% translucent layers stack into 75% opacityTwo 0.5-opacity fills overlap and measure at about 0.75, much darker than the design. Alpha composites as a_top + a_bottom x (1 - a_top), and any CSS `opacity` on the pane multiplies the result again.Read → 114Road width does not change with zoomA `weight: 3` road looks like an arterial at zoom 12 and like a hairline at 17, because pixels are fixed while metres per pixel halve each level. Three pixels span 115 m at z12 but only 3.6 m at z17.Read → 115Pin tip misses the coordinate by 12 pixelsA 24x40 pin whose tip is at bottom center points about 20 px south of the plotted coordinate. Leaflet's default `iconAnchor` is the icon center, `[12, 20]`, so the coordinate lands in the middle of the image instead of at the tip.Read → 116Eight-digit hex color is ignored by an older embedded browser`fillColor: '#1a6fd4cc'` renders in current Chrome, but the layer is completely invisible in a client's Chromium 76 embedded browser. SVG 1.1 does not accept `#RRGGBBAA`, so the `fill` value is treated as invalid and nothing is drawn.Read → 117A numeric filter never matches because the property is a stringA filter for districts with `pop >= 10000` draws nothing even though 40 features qualify, and no error is logged. The `pop` values arrived from CSV as strings, and Mapbox expressions are strongly typed with no implicit conversion.Read → 118glyphs 404 leaves every label blankIcons and polygons render but no text appears, and the network panel shows the glyph PBF ranges returning 404. The font stack name does not match the server's `fonts/` directory, and the Chinese Unicode ranges were never generated.Read → 119A missing sprite key silently drops the whole symbolSetting `icon-image: 'poi-restaurant'` renders nothing at all and logs no error, while `sprite.json` and `sprite.png` both return 200. The sprite table contains `restaurant`, not `poi-restaurant`, and an unmatched key skips the symbol including its text.Read → 120Printed map comes out 25% small because 96 and 72 DPI are mixedA map laid out at `794x1123` px, the A4 size at 96 DPI, prints at 75% of the page with the scale bar reading 0.75 km for 1 km. The PNG is interpreted at 72 DPI when placed into the PDF, so 794 px becomes 11.03 in instead of 8.27 in.Read →

Take the checklist with you.

All 120 notes are also available as a printable Chinese PDF.

Download the PDFChinese edition