Creating Composite Map Markers (Geoapify-Style)
Combine a background shape (droplet, circle, pin) with a foreground icon (Material Design, Ionicons, etc.) to create custom map markers, just like Geoapify does.
Method 1: SVG Composition (Recommended)
Section titled “Method 1: SVG Composition (Recommended)”Basic Pattern
Section titled “Basic Pattern”const createMarker = (iconPath: string, color: string) => { return `data:image/svg+xml,${encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="40" height="50" viewBox="0 0 40 50"> <!-- Background shape (droplet/pin) --> <path d="M20 0 C10 0 2 8 2 18 C2 30 20 50 20 50 S38 30 38 18 C38 8 30 0 20 0 Z" fill="${color}" stroke="#fff" stroke-width="2"/>
<!-- Foreground icon --> <path d="${iconPath}" fill="#fff" transform="translate(12, 10) scale(0.8)"/> </svg> `)}`;};
// Usage<LeafletMap markerIcon={createMarker(heartIconPath, '#FF69B4')} />Method 2: Using Material Design Icons
Section titled “Method 2: Using Material Design Icons”Step 1: Get Material Icon SVG Path
Section titled “Step 1: Get Material Icon SVG Path”From Material Symbols: https://fonts.google.com/icons
Example: favorite (heart) icon path:
M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35zStep 2: Create Marker with Icon
Section titled “Step 2: Create Marker with Icon”const materialIconMarker = (iconPath: string, bgColor: string, iconColor: string = '#fff') => { return `data:image/svg+xml,${encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="40" height="50" viewBox="0 0 40 50"> <!-- Droplet background --> <path d="M20 0 C10 0 2 8 2 18 C2 30 20 50 20 50 S38 30 38 18 C38 8 30 0 20 0 Z" fill="${bgColor}" stroke="#fff" stroke-width="2"/>
<!-- Material icon (24x24 viewport scaled and positioned) --> <g transform="translate(8, 7)"> <path d="${iconPath}" fill="${iconColor}"/> </g> </svg> `)}`;};
// Usageconst heartIcon = 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z';
<LeafletMap markerIcon={materialIconMarker(heartIcon, '#FF69B4')} markers={[[lat, lng, 'Romantic spot']]}/>Method 3: Using Ionicons (Already in Your Project!)
Section titled “Method 3: Using Ionicons (Already in Your Project!)”Step 1: Get Ionicon SVG
Section titled “Step 1: Get Ionicon SVG”From your node_modules or Ionicons CDN.
Example: location icon:
const locationIcon = 'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z';
const ioniconMarker = (iconName: string, iconPath: string, bgColor: string) => { return `data:image/svg+xml,${encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="40" height="50" viewBox="0 0 40 50"> <!-- Circular background --> <circle cx="20" cy="18" r="16" fill="${bgColor}" stroke="#fff" stroke-width="2"/> <path d="M20 18 L20 48" stroke="${bgColor}" stroke-width="3"/>
<!-- Ionicon (512x512 viewport, scaled down) --> <g transform="translate(5, 3) scale(0.06)"> <path d="${iconPath}" fill="#fff"/> </g> </svg> `)}`;};Method 4: Background Shape Variations
Section titled “Method 4: Background Shape Variations”Droplet/Teardrop (Classic Pin)
Section titled “Droplet/Teardrop (Classic Pin)”const dropletShape = ` M20 0 C10 0 2 8 2 18 C2 30 20 50 20 50 S38 30 38 18 C38 8 30 0 20 0 Z`;Circle with Stick (Modern Style)
Section titled “Circle with Stick (Modern Style)”const circleStickShape = ` <!-- Stick --> <line x1="20" y1="18" x2="20" y2="48" stroke="${color}" stroke-width="3"/> <!-- Circle --> <circle cx="20" cy="18" r="16" fill="${color}" stroke="#fff" stroke-width="2"/>`;Rounded Square
Section titled “Rounded Square”const roundedSquareShape = ` <rect x="4" y="2" width="32" height="32" rx="6" fill="${color}" stroke="#fff" stroke-width="2"/> <path d="M20 34 L20 48" stroke="${color}" stroke-width="3"/>`;Shield/Badge
Section titled “Shield/Badge”const shieldShape = ` <path d="M20 2 L4 8 L4 20 C4 32 20 44 20 44 S36 32 36 20 L36 8 Z" fill="${color}" stroke="#fff" stroke-width="2"/>`;Hexagon
Section titled “Hexagon”const hexagonShape = ` <path d="M20 2 L35 11 L35 29 L20 38 L5 29 L5 11 Z" fill="${color}" stroke="#fff" stroke-width="2"/> <line x1="20" y1="38" x2="20" y2="48" stroke="${color}" stroke-width="3"/>`;Complete Example: Material + Droplet
Section titled “Complete Example: Material + Droplet”// Material Design icon paths (from Google Fonts Icons)const MATERIAL_ICONS = { home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z', favorite: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z', restaurant: 'M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z', local_bar: 'M21 5V3H3v2l8 9v5H6v2h12v-2h-5v-5l8-9zM7.43 7L5.66 5h12.69l-1.78 2H7.43z', hotel: 'M7 13c1.66 0 3-1.34 3-3S8.66 7 7 7s-3 1.34-3 3 1.34 3 3 3zm12-6h-8v7H3V5H1v15h2v-3h18v3h2v-9c0-2.21-1.79-4-4-4z', place: 'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z', star: 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z', flag: 'M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z',};
// Helper function to create composite markerconst createCompositeMarker = ( iconPath: string, bgColor: string, shape: 'droplet' | 'circle' | 'square' | 'shield' = 'droplet') => { const shapes = { droplet: `<path d="M20 0 C10 0 2 8 2 18 C2 30 20 50 20 50 S38 30 38 18 C38 8 30 0 20 0 Z" fill="${bgColor}" stroke="#fff" stroke-width="2"/>`, circle: `<circle cx="20" cy="18" r="16" fill="${bgColor}" stroke="#fff" stroke-width="2"/><line x1="20" y1="34" x2="20" y2="48" stroke="${bgColor}" stroke-width="3"/>`, square: `<rect x="4" y="2" width="32" height="32" rx="6" fill="${bgColor}" stroke="#fff" stroke-width="2"/><path d="M20 34 L20 48" stroke="${bgColor}" stroke-width="3"/>`, shield: `<path d="M20 2 L4 8 L4 20 C4 32 20 44 20 44 S36 32 36 20 L36 8 Z" fill="${bgColor}" stroke="#fff" stroke-width="2"/>`, };
return `data:image/svg+xml,${encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="40" height="50" viewBox="0 0 40 50"> ${shapes[shape]} <g transform="translate(8, 7)"> <path d="${iconPath}" fill="#fff"/> </g> </svg> `)}`;};
// Usage examples<LeafletMap markerIcon={createCompositeMarker(MATERIAL_ICONS.home, '#FF69B4', 'droplet')} markers={[[lat, lng, 'Home']]}/>
<LeafletMap markerIcon={createCompositeMarker(MATERIAL_ICONS.local_bar, '#9B59B6', 'circle')} markers={[[lat, lng, 'Bar']]}/>
<LeafletMap markerIcon={createCompositeMarker(MATERIAL_ICONS.flag, '#E91E63', 'shield')} markers={[[lat, lng, 'Pride Location']]}/>Method 5: Using Lucide Icons (Also in Your Project!)
Section titled “Method 5: Using Lucide Icons (Also in Your Project!)”Lucide icons work the same way since they’re SVG paths:
// Lucide icon paths (24x24 viewbox)const LUCIDE_ICONS = { home: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z M9 22V12h6v10', heart: 'M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z', mapPin: 'M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z M12 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6z', flag: 'M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z M4 22v-7', star: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',};
const lucideMarker = (iconPath: string, bgColor: string) => { return `data:image/svg+xml,${encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="40" height="50" viewBox="0 0 40 50"> <path d="M20 0 C10 0 2 8 2 18 C2 30 20 50 20 50 S38 30 38 18 C38 8 30 0 20 0 Z" fill="${bgColor}" stroke="#fff" stroke-width="2"/> <g transform="translate(8, 7)"> <g stroke="#fff" fill="none" stroke-width="2"> <path d="${iconPath}"/> </g> </g> </svg> `)}`;};Method 6: Create Reusable Helper in Your UI Package
Section titled “Method 6: Create Reusable Helper in Your UI Package”import { MATERIAL_ICONS, LUCIDE_ICONS, IONICONS } from './icon-paths';
export type MarkerShape = 'droplet' | 'circle' | 'square' | 'shield' | 'hexagon';export type IconLibrary = 'material' | 'lucide' | 'ionicons';
interface CreateMarkerOptions { icon: string; iconLibrary: IconLibrary; bgColor: string; shape?: MarkerShape; iconColor?: string;}
export const createMarker = ({ icon, iconLibrary, bgColor, shape = 'droplet', iconColor = '#fff',}: CreateMarkerOptions): string => { const iconPaths = { material: MATERIAL_ICONS, lucide: LUCIDE_ICONS, ionicons: IONICONS, };
const iconPath = iconPaths[iconLibrary][icon]; if (!iconPath) { throw new Error(`Icon "${icon}" not found in ${iconLibrary}`); }
const shapes = { droplet: `<path d="M20 0 C10 0 2 8 2 18 C2 30 20 50 20 50 S38 30 38 18 C38 8 30 0 20 0 Z" fill="${bgColor}" stroke="#fff" stroke-width="2"/>`, circle: `<circle cx="20" cy="18" r="16" fill="${bgColor}" stroke="#fff" stroke-width="2"/><line x1="20" y1="34" x2="20" y2="48" stroke="${bgColor}" stroke-width="3"/>`, square: `<rect x="4" y="2" width="32" height="32" rx="6" fill="${bgColor}" stroke="#fff" stroke-width="2"/><path d="M20 34 L20 48" stroke="${bgColor}" stroke-width="3"/>`, shield: `<path d="M20 2 L4 8 L4 20 C4 32 20 44 20 44 S36 32 36 20 L36 8 Z" fill="${bgColor}" stroke="#fff" stroke-width="2"/>`, hexagon: `<path d="M20 2 L35 11 L35 29 L20 38 L5 29 L5 11 Z" fill="${bgColor}" stroke="#fff" stroke-width="2"/><line x1="20" y1="38" x2="20" y2="48" stroke="${bgColor}" stroke-width="3"/>`, };
return `data:image/svg+xml,${encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="40" height="50" viewBox="0 0 40 50"> ${shapes[shape]} <g transform="translate(8, 7)"> <path d="${iconPath}" fill="${iconColor}"/> </g> </svg> `)}`;};
// Usageimport { createMarker } from '@cloudalt-frontend/ui-pride-city/LeafletMap/icons';
<LeafletMap markerIcon={createMarker({ icon: 'home', iconLibrary: 'material', bgColor: '#FF69B4', shape: 'droplet', })}/>Advantages
Section titled “Advantages”✅ No external dependencies - Pure SVG composition
✅ Fully customizable - Control every aspect
✅ Icon libraries you already have - Material, Lucide, Ionicons
✅ Brand consistency - Use your exact colors
✅ Performance - Data URLs load instantly
✅ No licensing issues - Use open source icons
Next Steps
Section titled “Next Steps”Would you like me to:
- ✅ Create a full icon set with Material Design icons + droplet backgrounds?
- ✅ Build the
createMarkerhelper function inui-pride-city? - ✅ Add pre-built marker presets for common use cases (bar, restaurant, hotel, etc.)?
- ✅ Create brand-specific colors for each division?
- ✅ Add Storybook examples showing all combinations?
Let me know and I’ll implement it!