22 KiB
Map System - Comprehensive Update & Fix Plan
Based on layer testing results from LAYER_NOTES.md
Date: January 16, 2026
Executive Summary
Current Status:
- ✅ 6/7 base layers working (1 broken: Polish Orthophoto High Resolution)
- ⚠️ 2/9 overlay layers working (2 very slow, 5 not tested, 2 broken)
- 🎯 Priority: Fix broken layers, optimize slow WMS services, remove LP-Portal layers
Key Issues Identified:
- Polish Orthophoto High Resolution completely broken
- Polish Cadastral Data servers extremely slow (both servers)
- Polish Spatial Planning layer not working
- LP-Portal layers not tested/documented - likely region-specific
- No caching or performance optimization for WMS layers
- Missing zoom level restrictions causing tile request failures
Phase 1: Critical Fixes (Week 1)
1.1 Fix Polish Orthophoto High Resolution
Issue: Doesn't load at all
Root Cause: Likely incorrect WMTS parameters or service endpoint change
Action Plan:
- Test GetCapabilities response:
curl "https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMTS/HighResolution?Service=WMTS&Request=GetCapabilities" - Compare with Standard Resolution working configuration
- Check for:
- Different tile matrix sets
- Different available zoom levels
- Format differences (jpeg vs png)
- Authentication requirements
Implementation:
// Test if service requires different parameters
{
name: "🇵🇱 Polish Orthophoto (High Resolution)",
url: "https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMTS/HighResolution?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ORTO&STYLE=default&TILEMATRIXSET=EPSG:3857&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image/jpeg",
maxZoom: 19, // May need adjustment based on GetCapabilities
minZoom: 15, // High-res often only available at higher zoom
}
Success Criteria: Layer loads tiles without errors
1.2 Fix Polish Spatial Planning Layer
Issue: Doesn't work or extremely slow
Service: https://mapy.geoportal.gov.pl/wss/ext/KrajowaIntegracjaMiejscowychPlanowZagospodarowaniaPrzestrzennego
Action Plan:
- Verify service is still active via GetCapabilities
- Test with simplified layer list (may be requesting too many layers)
- Check if service moved to new endpoint
- Test with different WMS versions (1.1.1 vs 1.3.0)
Implementation:
// Simplified layer request
{
name: "🏗️ Polish Spatial Planning",
type: "wms",
url: "https://mapy.geoportal.gov.pl/wss/ext/KrajowaIntegracjaMiejscowychPlanowZagospodarowaniaPrzestrzennego",
params: {
layers: "raster", // Start with just raster
format: "image/png",
transparent: true,
version: "1.3.0",
},
maxZoom: 18, // Limit to prevent overload
}
Success Criteria: Layer loads or is removed if permanently unavailable
1.3 Optimize Polish Cadastral Data Performance
Issue: Both servers very slow, currently only work up to zoom 18
Impact: Core functionality for land surveying projects
Action Plan:
- Implement tile loading indicators
- Add request debouncing
- Consider caching strategy
- Test alternate GUGiK services
- (Future) Enable zoom 19-20 with proper optimization
Implementation:
// Update both cadastral servers with performance optimizations
{
name: "📋 Polish Cadastral Data (Działki) - Server 2",
type: "wms",
url: "https://integracja.gugik.gov.pl/cgi-bin/KrajowaIntegracjaEwidencjiGruntow",
params: {
layers: "dzialki,numery_dzialek,budynki", // Simplified - remove slow layers
format: "image/png",
transparent: true,
version: "1.3.0",
},
maxZoom: 18, // Current working limit (TODO: extend to 20 with optimization)
minZoom: 13, // Don't load at far zoom levels
opacity: 0.8,
}
Additional Optimizations:
- Add WMS tiled parameter:
tiled: true - Reduce requested layers to essential only
- Implement progressive loading (load parcels first, then details)
Success Criteria: Acceptable load times (<3s) at zoom 15-18, prepare for zoom 20 support
Phase 2: Layer Management (Week 2)
2.1 Remove/Document LP-Portal Layers
Issue: 4 LP-Portal layers never tested, likely region-specific (Nowy Sącz)
Action Plan:
- Test if LP-Portal layers work outside Nowy Sącz region
- If region-specific: Move to separate optional configuration
- Document geographic limitations
- Consider conditional loading based on map center coordinates
Options:
Option A - Remove Entirely:
// Remove from mapLayers.overlays array:
// - LP-Portal Roads
// - LP-Portal Street Names
// - LP-Portal Parcels
// - LP-Portal Survey Markers
Option B - Conditional Loading:
// Only show LP-Portal layers when in Nowy Sącz region
const isInNowySecz = (lat, lng) => {
return lat >= 49.5 && lat <= 49.7 && lng >= 20.5 && lng <= 20.8;
};
// Filter overlays based on location
const availableOverlays = mapLayers.overlays.filter(layer => {
if (layer.name.includes('LP-Portal')) {
return isInNowySecz(mapCenter[0], mapCenter[1]);
}
return true;
});
Recommendation: Option B - Keep but make conditional
Success Criteria: Only relevant layers shown to users
2.2 Reorganize Layer Categories
Current: Mixed organization, no clear hierarchy
Proposed: Clear categorization with user-friendly names
New Structure:
export const mapLayers = {
base: [
// International Base Maps
{ name: "OpenStreetMap", ... },
{ name: "🌍 Google Satellite", ... },
{ name: "🌍 Google Hybrid", ... },
{ name: "🗺️ Esri Satellite", ... },
{ name: "🗺️ Topographic (CARTO)", ... },
// Polish Aerial Imagery
{ name: "🇵🇱 Orthophoto (Standard)", ... },
{ name: "🇵🇱 Orthophoto (High-Res)", ... }, // After fix
],
overlays: {
government: [
{ name: "📋 Cadastral Data (Official)", ... },
{ name: "🏗️ Spatial Planning", ... },
],
utility: [
{ name: "🛣️ Google Roads", ... },
],
regional: [ // Only shown in specific regions
{ name: "🏘️ LP-Portal Roads", region: "nowysacz", ... },
{ name: "🏘️ LP-Portal Street Names", region: "nowysacz", ... },
{ name: "🏘️ LP-Portal Parcels", region: "nowysacz", ... },
{ name: "🏘️ LP-Portal Survey Markers", region: "nowysacz", ... },
]
}
};
Success Criteria: Clearer user experience, better organization
Phase 3: Performance Optimization (Week 3)
3.1 Implement Tile Caching
Goal: Reduce redundant WMS requests
Implementation:
// Add to WMSLayer component
const WMSLayer = ({ url, params, opacity, attribution }) => {
const map = useMap();
useEffect(() => {
const wmsOptions = {
// ... existing options ...
// Add caching headers
crossOrigin: true,
updateWhenIdle: true,
updateWhenZooming: false,
keepBuffer: 2, // Keep tiles loaded from 2 screens away
};
const wmsLayer = L.tileLayer.wms(url, wmsOptions);
wmsLayer.addTo(map);
return () => map.removeLayer(wmsLayer);
}, [map, url, params, opacity, attribution]);
};
Success Criteria: 30% reduction in WMS requests on pan/zoom
3.2 Add Loading States
Goal: User feedback during slow WMS loads
Implementation:
// New LoadingOverlay component
function MapLoadingOverlay({ isLoading }) {
if (!isLoading) return null;
return (
<div className="absolute top-16 right-4 bg-white dark:bg-gray-800 rounded-lg shadow-lg px-4 py-2 z-[1000]">
<div className="flex items-center gap-2">
<div className="animate-spin rounded-full h-4 w-4 border-2 border-blue-500 border-t-transparent"></div>
<span className="text-sm text-gray-700 dark:text-gray-300">Loading map layers...</span>
</div>
</div>
);
}
// Track loading state in LeafletMap
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
map.on('layeradd', () => setIsLoading(true));
map.on('load', () => setIsLoading(false));
}, [map]);
Success Criteria: Visual feedback for all layer loads
3.3 Implement Progressive Layer Loading
Goal: Load essential layers first, details later
Strategy:
- Zoom 1-12: Base map only
- Zoom 13-15: + Basic cadastral boundaries
- Zoom 16-18: + Parcel numbers, buildings
- Zoom 19-20: + Survey markers, detailed overlays
Implementation:
// Auto-enable/disable overlays based on zoom
function ZoomBasedOverlayManager() {
const map = useMap();
const [currentZoom, setCurrentZoom] = useState(map.getZoom());
useEffect(() => {
map.on('zoomend', () => {
const zoom = map.getZoom();
setCurrentZoom(zoom);
// Auto-manage overlay visibility
if (zoom < 13) {
// Disable all overlays at far zoom
disableAllOverlays();
} else if (zoom >= 16) {
// Enable cadastral at close zoom
enableCadastralLayer();
}
});
}, [map]);
}
Success Criteria: Smooth performance at all zoom levels
Phase 4: Enhanced Features (Week 4)
4.1 Dynamic Opacity Controls
Goal: User-adjustable layer transparency
Implementation:
// LayerOpacityControl component
function LayerOpacityControl({ layerName, currentOpacity, onOpacityChange }) {
return (
<div className="flex items-center gap-2 px-2 py-1">
<label className="text-xs text-gray-600 dark:text-gray-400 w-24 truncate">
{layerName}
</label>
<input
type="range"
min="0"
max="100"
value={currentOpacity * 100}
onChange={(e) => onOpacityChange(e.target.value / 100)}
className="flex-1 h-1"
/>
<span className="text-xs text-gray-500 w-8 text-right">
{Math.round(currentOpacity * 100)}%
</span>
</div>
);
}
// Add to layer control
<LayersControl position="topright">
<Overlay name="Cadastral Data">
<WMSLayer {...layer} opacity={cadastralOpacity} />
</Overlay>
<LayerOpacityControl
layerName="Cadastral"
currentOpacity={cadastralOpacity}
onOpacityChange={setCadastralOpacity}
/>
</LayersControl>
Success Criteria: User can adjust opacity for all overlay layers
4.2 Layer Information Panels
Goal: Show layer metadata, legends, data source info
Implementation:
// Layer metadata structure
const layerMetadata = {
"Polish Cadastral Data": {
title: "Polish Cadastral Data (Działki)",
description: "Official land parcel boundaries and property information from GUGiK",
dataSource: "Główny Urząd Geodezji i Kartografii",
updateFrequency: "Daily",
coverage: "Poland nationwide",
legend: "/images/legends/cadastral.png",
moreInfo: "https://www.gugik.gov.pl/",
usageNotes: "Best viewed at zoom levels 15-18. Performance may vary.",
}
};
// InfoButton component next to layer name
<LayersControl>
<Overlay name={
<div className="flex items-center gap-1">
📋 Cadastral Data
<button onClick={() => showLayerInfo('Polish Cadastral Data')} className="...">
ℹ️
</button>
</div>
}>
...
</Overlay>
</LayersControl>
Success Criteria: Users understand what each layer shows
4.3 Error Handling & Fallbacks
Goal: Graceful degradation when layers fail
Implementation:
// WMSLayer with error handling
function WMSLayer({ url, params, opacity, attribution, fallbackLayer }) {
const map = useMap();
const [hasError, setHasError] = useState(false);
useEffect(() => {
const wmsLayer = L.tileLayer.wms(url, wmsOptions);
// Track tile errors
wmsLayer.on('tileerror', (error) => {
console.error(`WMS tile error for ${params.layers}:`, error);
setHasError(true);
// Show user notification
showNotification({
type: 'warning',
message: `Layer "${params.layers}" is experiencing issues`,
duration: 5000
});
});
wmsLayer.addTo(map);
// If too many errors, switch to fallback
if (hasError && fallbackLayer) {
setTimeout(() => {
map.removeLayer(wmsLayer);
fallbackLayer.addTo(map);
}, 3000);
}
return () => map.removeLayer(wmsLayer);
}, [map, url, params, hasError]);
}
Success Criteria: No silent failures, users informed of issues
Phase 5: Code Quality (Week 5)
5.1 Consolidate Map Components
Current Issue: Multiple similar map components (ComprehensivePolishMap, ImprovedPolishOrthophotoMap, etc.)
Action Plan:
-
Audit all map components:
- LeafletMap.js (main)
- ProjectMap.js (wrapper)
- ComprehensivePolishMap.js
- ImprovedPolishOrthophotoMap.js
- PolishOrthophotoMap.js
- AdvancedPolishOrthophotoMap.js
- TransparencyDemoMap.js
- CustomWMTSMap.js
- EnhancedLeafletMap.js
-
Determine which are:
- Production (keep)
- Deprecated (remove)
- Experimental (move to /docs/examples)
Recommendation:
KEEP:
- LeafletMap.js (main production component)
- ProjectMap.js (SSR wrapper)
MOVE TO /docs/examples:
- TransparencyDemoMap.js (example of opacity controls)
- CustomWMTSMap.js (example of custom WMTS)
DEPRECATE/REMOVE:
- ComprehensivePolishMap.js (superseded by LeafletMap)
- ImprovedPolishOrthophotoMap.js (experimental)
- PolishOrthophotoMap.js (old version)
- AdvancedPolishOrthophotoMap.js (experimental)
- EnhancedLeafletMap.js (duplicate?)
Success Criteria: Single source of truth for map rendering
5.2 Improve WMTS Capabilities Parsing
Current Issue: wmtsCapabilities.js has placeholder code
Options:
Option A - Complete XML Parsing:
export async function parseWMTSCapabilities(url) {
const response = await fetch(`${url}?Service=WMTS&Request=GetCapabilities`);
const xmlText = await response.text();
const parser = new DOMParser();
const xml = parser.parseFromString(xmlText, 'text/xml');
const layers = Array.from(xml.querySelectorAll('Layer')).map(layer => ({
id: layer.querySelector('Identifier')?.textContent,
title: layer.querySelector('Title')?.textContent,
formats: Array.from(layer.querySelectorAll('Format')).map(f => f.textContent),
tileMatrixSets: Array.from(layer.querySelectorAll('TileMatrixSet')).map(t => t.textContent),
}));
return { layers };
}
Option B - Remove and Document:
- Remove wmtsCapabilities.js
- Document WMTS configuration in MAP_LAYERS.md
- Use manual configuration (current working approach)
Recommendation: Option B - Keep it simple, current approach works
Success Criteria: No dead code, clear documentation
5.3 Add TypeScript/JSDoc Types
Goal: Better IDE support and type safety
Implementation:
/**
* @typedef {Object} LayerConfig
* @property {string} name - Display name for the layer
* @property {'tile'|'wms'} type - Layer type
* @property {string} url - Service URL
* @property {string} attribution - Attribution HTML
* @property {number} [maxZoom=20] - Maximum zoom level
* @property {number} [minZoom=0] - Minimum zoom level
* @property {number} [opacity=1.0] - Layer opacity (0-1)
* @property {boolean} [checked=false] - Default enabled state
* @property {Object} [params] - WMS parameters (for WMS layers)
*/
/**
* @typedef {Object} MapLayersConfig
* @property {LayerConfig[]} base - Base layer options
* @property {LayerConfig[]} overlays - Overlay layer options
*/
/** @type {MapLayersConfig} */
export const mapLayers = {
base: [...],
overlays: [...]
};
Success Criteria: Better autocomplete and error detection
Phase 6: Testing & Documentation (Week 6)
6.1 Create Layer Test Suite
Goal: Automated testing of layer availability
Implementation:
// __tests__/map-layers.test.js
describe('Map Layers', () => {
describe('Base Layers', () => {
test('OSM tiles are accessible', async () => {
const response = await fetch('https://a.tile.openstreetmap.org/15/17560/11326.png');
expect(response.status).toBe(200);
});
test('Polish Orthophoto Standard is accessible', async () => {
const url = 'https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMTS/StandardResolution?Service=WMTS&Request=GetCapabilities';
const response = await fetch(url);
expect(response.status).toBe(200);
});
});
describe('WMS Overlays', () => {
test('Cadastral WMS GetCapabilities works', async () => {
const url = 'https://integracja.gugik.gov.pl/cgi-bin/KrajowaIntegracjaEwidencjiGruntow?Service=WMS&Request=GetCapabilities';
const response = await fetch(url);
expect(response.status).toBe(200);
expect(response.headers.get('content-type')).toContain('xml');
});
});
});
Success Criteria: All layers validated before deployment
6.2 Update Documentation
Files to Update:
- MAP_LAYERS.md - Add troubleshooting section
- LAYER_NOTES.md - Keep updated with testing
- README.md - Add maps usage section
New Documentation:
## Troubleshooting Map Layers
### Slow Loading Cadastral Data
- **Cause:** GUGiK WMS servers are resource-limited
- **Solution:** Only enable at zoom 15+, limit to essential layers
- **Alternative:** Pre-cache frequently used areas
### Polish Orthophoto Not Loading
- **Check:** Zoom level (works up to 19, not 20)
- **Check:** Network connectivity to geoportal.gov.pl
- **Alternative:** Use Google Satellite or Esri
### Layer Control Not Showing
- **Cause:** Map container too small
- **Solution:** Minimum map height of 400px recommended
Success Criteria: Users can self-service common issues
Implementation Priority Matrix
| Priority | Phase | Task | Impact | Effort | Status |
|---|---|---|---|---|---|
| 🔴 P0 | 1 | Fix Polish Orthophoto High-Res | High | Low | Not Started |
| 🔴 P0 | 1 | Add zoom restrictions to Cadastral | High | Low | Not Started |
| 🟡 P1 | 1 | Fix/Remove Spatial Planning | Medium | Medium | Not Started |
| 🟡 P1 | 2 | Document LP-Portal region limits | Medium | Low | Not Started |
| 🟡 P1 | 3 | Add loading indicators | Medium | Low | Not Started |
| 🟢 P2 | 2 | Reorganize layer categories | Low | Medium | Not Started |
| 🟢 P2 | 4 | Add opacity controls | Low | Medium | Not Started |
| 🟢 P2 | 4 | Add layer info panels | Low | High | Not Started |
| 🟢 P3 | 5 | Consolidate map components | Low | High | Not Started |
| 🟢 P3 | 6 | Add automated tests | Low | Medium | Not Started |
Quick Wins (Do First)
These can be implemented in 1-2 hours with immediate impact:
-
Add minZoom to performance-heavy layers
- Prevent loading at far zoom levels (minZoom: 13 for Cadastral)
- Reduce unnecessary requests at distant zoom
-
Optimize Cadastral layer requests
- Reduce number of requested WMS layers
- Add tiled parameter for better performance
-
Remove broken Spatial Planning layer
- If GetCapabilities fails, just remove it
- Better than showing broken functionality
-
Update layer names for clarity
- "Polish Orthophoto Standard" → "🇵🇱 Aerial Imagery (Standard)"
- Better user understanding
-
Add loading spinner to ProjectMap
- Copy LoadingOverlay component
- Better UX during slow loads
-
Verify current zoom limits
- Document actual working zoom ranges per layer
- Note: Goal is zoom 20 for all layers (future optimization)
Success Metrics
Performance
- All base layers load in <2 seconds
- Cadastral overlays load in <5 seconds at zoom 15-18
- No console errors for working layers
- 90%+ tile success rate
User Experience
- Layer control accessible on all screen sizes
- Clear feedback during loading
- No broken/blank layers in production
- Layer purposes clear from names/descriptions
Code Quality
- Single production map component
- All map files under 500 lines
- JSDoc types for all exports
- 80%+ test coverage for layer configs
Rollout Plan
Week 1: Emergency Fixes
- Fix critical broken layers
- Add zoom restrictions
- Remove non-working layers
Week 2: Optimization
- Implement caching
- Add loading states
- Progressive loading
Week 3: Features
- Opacity controls
- Layer info panels
- Error handling
Week 4: Cleanup
- Consolidate components
- Remove experimental code
- Update documentation
Week 5: Testing
- Automated tests
- User acceptance testing
- Performance benchmarking
Week 6: Release
- Deploy to production
- Monitor performance
- Gather user feedback
Rollback Strategy
If issues occur:
- Keep old mapLayers.js as
mapLayers.backup.js - Feature flags for new functionality
- Incremental rollout - enable for admin users first
- Quick disable - config flag to revert to old layers
Future Considerations
Potential New Features
- Universal zoom 20 support for all layers
- Optimize WMS services to handle zoom 19-20
- Implement tile prefetching and caching
- Add progressive detail loading at high zoom
- Save user layer preferences
- Share map view URLs (with layers/zoom)
- Export map as image/PDF
- Offline tile caching
- Custom layer upload (GeoJSON, KML)
Alternative Services to Explore
- Planet imagery (if budget allows)
- Bing Maps aerial imagery
- Additional Polish regional services
- CORS proxies for restricted services
Advanced Optimizations
- Service worker for tile caching
- WebGL rendering for better performance
- Vector tiles instead of raster
- CDN for frequently accessed tiles
Notes
- LP-Portal layers appear to be Nowy Sącz specific - need regional filtering
- Polish government servers are consistently slow - can't fix, only mitigate
- Google services are unofficial - may break without notice
- WMTS is more performant than WMS - prefer when available
- Zoom 20 support: Long-term goal for all layers; currently some layers work only to zoom 18-19
- Requires server-side optimization or caching strategy
- May need to implement client-side tile scaling/interpolation
- Keep LAYER_NOTES.md updated as testing continues
Approval & Sign-off
- Technical review completed
- Performance benchmarks met
- Documentation updated
- Stakeholder approval
- Ready for production deployment
Last Updated: January 16, 2026
Next Review: After Phase 1 completion