Web Mapping
This guide demonstrates using Palettize to create colormaps for web mapping applications including TiTiler, MapLibre GL JS, and Observable Plot.
TiTiler Dynamic Tiling
Section titled “TiTiler Dynamic Tiling”Basic COG Visualization
Section titled “Basic COG Visualization”# Generate TiTiler colormap parameterpalettize create viridis --format titiler --steps 256Use in a tile URL:
https://titiler.example.com/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=https://example.com/data.tif&rescale=0,255&colormap=%7B%220%22%3A%22%23440154%22...%7DPython Integration
Section titled “Python Integration”from palettize import create_colormap, get_scaler_by_name, get_exporterimport httpx
# Create colormapcmap = create_colormap(preset="viridis")scaler = get_scaler_by_name("linear", domain_min=0, domain_max=255)exporter = get_exporter("titiler")
colormap_param = exporter.export(cmap, scaler, 0, 255, {"num_colors": 256})
# Build tile URLcog_url = "https://example.com/elevation.tif"base_url = "https://titiler.example.com/cog/tiles/WebMercatorQuad"tile_template = f"{base_url}/{{z}}/{{x}}/{{y}}.png?url={cog_url}&rescale=0,4000&{colormap_param}"
print(tile_template)Leaflet with TiTiler
Section titled “Leaflet with TiTiler”<!DOCTYPE html><html><head> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script></head><body> <div id="map" style="height: 100vh;"></div> <script> const map = L.map('map').setView([40, -100], 4);
// Base layer L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// TiTiler layer with Palettize colormap const cogUrl = 'https://example.com/elevation.tif'; const colormap = '%7B%220%22%3A%22%23440154%22...%7D'; // From palettize
L.tileLayer( `https://titiler.example.com/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=${cogUrl}&rescale=0,4000&colormap=${colormap}`, { opacity: 0.7 } ).addTo(map); </script></body></html>MapLibre GL JS
Section titled “MapLibre GL JS”Vector Tile Styling
Section titled “Vector Tile Styling”# Generate MapLibre expressionpalettize create viridis --format mapgl --output style.json \ --domain 0,100 --steps 11 -O property_name=valueFull Example
Section titled “Full Example”<!DOCTYPE html><html><head> <script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script> <link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" /></head><body> <div id="map" style="height: 100vh;"></div> <script> // Color expression from Palettize const colorExpression = ["interpolate", ["linear"], ["get", "temperature"], -20, "#053061", -10, "#2166ac", 0, "#f7f7f7", 10, "#b2182b", 20, "#67001f" ];
const map = new maplibregl.Map({ container: 'map', style: 'https://demotiles.maplibre.org/style.json', center: [-100, 40], zoom: 4 });
map.on('load', () => { map.addSource('weather', { type: 'vector', url: 'https://example.com/weather.json' });
map.addLayer({ id: 'temperature', type: 'fill', source: 'weather', 'source-layer': 'data', paint: { 'fill-color': colorExpression, 'fill-opacity': 0.8 } }); }); </script></body></html>Choropleth Map
Section titled “Choropleth Map”from palettize import create_colormap, get_scaler_by_name, get_exporterimport json
# Create diverging colormap for population changecmap = create_colormap(colors=["#b2182b", "#f7f7f7", "#2166ac"])scaler = get_scaler_by_name("linear", domain_min=-50, domain_max=50)exporter = get_exporter("mapgl")
expression = exporter.export( cmap, scaler, -50, 50, {"num_colors": 11, "property_name": "pop_change"})
# Use in MapLibre stylestyle_layer = { "id": "choropleth", "type": "fill", "source": "counties", "paint": { "fill-color": json.loads(expression), "fill-outline-color": "#000000" }}Observable Plot
Section titled “Observable Plot”Basic Heatmap
Section titled “Basic Heatmap”# Generate Observable scalepalettize create viridis --format observable --output scale.json \ --domain 0,100 --steps 10Observable Notebook
Section titled “Observable Notebook”// Import the scale definitionconst scaleConfig = { "type": "linear", "domain": [0, 100], "range": ["#440154", "#482878", "#3e4989", "#31688e", "#26828e", "#1f9e89", "#35b779", "#6ece58", "#b5de2b", "#fde725"]};
// Use with PlotPlot.plot({ color: { type: scaleConfig.type, domain: scaleConfig.domain, range: scaleConfig.range }, marks: [ Plot.cell(data, {x: "x", y: "y", fill: "value"}) ]})Diverging Scale
Section titled “Diverging Scale”palettize create RdBu --format observable --output diverging.json \ --domain -10,10 -O type=diverging -O pivot=0// Diverging temperature anomalyPlot.plot({ color: { type: "diverging", domain: [-10, 10], range: ["#053061", "#2166ac", "#4393c3", "#92c5de", "#d1e5f0", "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#67001f"], pivot: 0 }, marks: [ Plot.dot(anomalies, {x: "lon", y: "lat", fill: "temp_anomaly"}) ]})Google Earth Engine
Section titled “Google Earth Engine”Satellite Imagery
Section titled “Satellite Imagery”# Generate GEE visualizationpalettize create viridis --format gee --output viz.js \ --domain 0,3000 --steps 10// In GEE Code Editorvar palettize_viz = { min: 0, max: 3000, palette: ['440154', '482878', '3e4989', '31688e', '26828e', '1f9e89', '35b779', '6ece58', 'b5de2b', 'fde725']};
var dem = ee.Image('USGS/SRTMGL1_003');Map.addLayer(dem, palettize_viz, 'Elevation');NDVI Time Series
Section titled “NDVI Time Series”# Green vegetation palettepalettize create --colors "#8B4513,#FFD700,#228B22,#006400" \ --format gee --output ndvi_viz.js --domain -0.2,0.8var ndvi_viz = { min: -0.2, max: 0.8, palette: ['8B4513', 'FFD700', '228B22', '006400']};
var ndvi = sentinel2.normalizedDifference(['B8', 'B4']);Map.addLayer(ndvi, ndvi_viz, 'NDVI');Complete Web App Example
Section titled “Complete Web App Example”Flask + TiTiler + Leaflet
Section titled “Flask + TiTiler + Leaflet”from flask import Flask, render_templatefrom palettize import create_colormap, get_scaler_by_name, get_exporter
app = Flask(__name__)
@app.route('/')def index(): # Generate colormap cmap = create_colormap(preset="viridis") scaler = get_scaler_by_name("linear", domain_min=0, domain_max=255) exporter = get_exporter("titiler")
colormap_param = exporter.export(cmap, scaler, 0, 255, {"num_colors": 256})
return render_template('map.html', colormap=colormap_param)<!DOCTYPE html><html><head> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script></head><body> <div id="map" style="height: 100vh;"></div> <script> const map = L.map('map').setView([40, -100], 4); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
const cogUrl = 'https://example.com/data.tif'; const colormap = '{{ colormap | safe }}';
L.tileLayer( `https://titiler.example.com/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=${cogUrl}&${colormap}`, { opacity: 0.7 } ).addTo(map); </script></body></html>Tips for Web Mapping
Section titled “Tips for Web Mapping”Performance
Section titled “Performance”- Use fewer color steps (10-20) for faster rendering
- Pre-generate colormaps; don’t compute per request
- Cache colormap parameters
Color Accessibility
Section titled “Color Accessibility”# Use colorblind-safe presetspalettize create viridis ... # Goodpalettize create cividis ... # Better for colorblindnessResponsive Legends
Section titled “Responsive Legends”Generate discrete stops for legend display:
# Get legend colorscmap = create_colormap(preset="viridis")legend_colors = []for i in range(5): pos = i / 4 val = domain_min + pos * (domain_max - domain_min) color = cmap.get_color(pos) legend_colors.append({"value": val, "color": color})