Add Markers & Pins
Learn how to display custom markers on your map, handle user interactions, and implement clustering for better performance with many markers.
What You'll Build
An app that displays:
- Custom markers at specific locations
- Different marker styles and icons
- Tap handling to show marker information
- Marker clustering (optional, for large datasets)
Time to complete: ~20 minutes
Prerequisites
- Completed Your First Map tutorial
- Basic understanding of map coordinates
Choose Your Platform
📱 iOS (Swift)
Step 1: Display a Single Marker with GLMapImage
The simplest way to add a marker is using GLMapImage
:
import UIKit
import GLMap
import GLMapSwift
class ViewController: UIViewController {
var mapView: GLMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Setup map view
mapView = GLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)
// Center on San Francisco
let sanFrancisco = GLMapGeoPoint(lat: 37.7749, lon: -122.4194)
mapView.mapGeoCenter = sanFrancisco
mapView.mapZoomLevel = 12
// Add a marker
addMarker(at: sanFrancisco, imageName: "pin1")
}
func addMarker(at location: GLMapGeoPoint, imageName: String) {
// Load marker image from bundle
guard let image = UIImage(named: imageName) else {
print("Image \(imageName) not found")
return
}
// Create GLMapImage
let marker = GLMapImage()
marker.setImage(image)
marker.position = GLMapPoint(lat: location.lat, lon: location.lon)
// Center the marker at its position
marker.offset = CGPoint(x: image.size.width / 2, y: image.size.height)
// Add to map
mapView.add(marker)
}
}
Step 2: Use SVG for Scalable Markers
For better quality at different zoom levels, use SVG:
func addSVGMarker(at location: GLMapGeoPoint, svgName: String) {
guard let svgPath = Bundle.main.path(forResource: svgName, ofType: "svg"),
let vectorImage = GLMapVectorImageFactory.shared.image(fromSvg: svgPath) else {
print("SVG \(svgName) not found")
return
}
let marker = GLMapImage()
marker.setImage(vectorImage, for: mapView)
marker.position = GLMapPoint(lat: location.lat, lon: location.lon)
marker.offset = vectorImage.offset
mapView.add(marker)
}
Step 3: Handle Marker Taps
To detect when a marker is tapped:
override func viewDidLoad() {
super.viewDidLoad()
// ... previous setup code ...
// Add tap gesture handler
mapView.setTapGestureBlock { [weak self] gesture in
self?.handleMapTap(gesture)
}
}
func handleMapTap(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: mapView)
// Get objects at tap location
let objects = mapView.objects(at: point)
for object in objects {
if let marker = object as? GLMapImage {
// Marker was tapped
showMarkerInfo(marker)
break
}
}
}
func showMarkerInfo(_ marker: GLMapImage) {
let alert = UIAlertController(
title: "Marker Tapped",
message: "Position: \(marker.position)",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
Step 4: Add Multiple Markers with GLMapImageGroup
For better performance with many markers, use GLMapImageGroup
:
func addMultipleMarkers() {
let locations = [
("Golden Gate Bridge", GLMapGeoPoint(lat: 37.8199, lon: -122.4783)),
("Alcatraz", GLMapGeoPoint(lat: 37.8267, lon: -122.4230)),
("Fisherman's Wharf", GLMapGeoPoint(lat: 37.8080, lon: -122.4177)),
("Lombard Street", GLMapGeoPoint(lat: 37.8021, lon: -122.4187))
]
// Load images
guard let pin1 = UIImage(named: "pin1"),
let pin2 = UIImage(named: "pin2"),
let pin3 = UIImage(named: "pin3") else {
return
}
// Create image group
let imageGroup = GLMapImageGroup()
imageGroup.images = [pin1, pin2, pin3]
for (index, (_, location)) in locations.enumerated() {
let data = GLMapMarkerData()
data.point = GLMapPoint(lat: location.lat, lon: location.lon)
data.imageIndex = UInt32(index % 3) // Cycle through images
data.offset = GLMapPoint(x: pin1.size.width / 2, y: pin1.size.height)
imageGroup.add(data)
}
mapView.add(imageGroup)
}
Step 5: Marker Clustering (Optional)
For large datasets (100+ markers), use clustering:
func setupClusteredMarkers() {
let markerLayer = GLMapMarkerLayer()
// Configure clustering
markerLayer.clusteringEnabled = true
markerLayer.clusteringRadius = 30 // pixels
// Add markers
for i in 0..<1000 {
let lat = 37.7749 + Double.random(in: -0.1...0.1)
let lon = -122.4194 + Double.random(in: -0.1...0.1)
let marker = GLMapMarker()
marker.point = GLMapPoint(lat: lat, lon: lon)
marker.text = "Marker \(i)"
markerLayer.add(marker)
}
mapView.add(markerLayer)
}
🤖 Android (Kotlin)
Step 1: Display a Single Marker
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import globus.glmap.GLMapImage
import globus.glmap.GLMapView
import globus.glmap.MapPoint
class MainActivity : AppCompatActivity() {
private lateinit var mapView: GLMapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mapView = findViewById(R.id.map_view)
// Center on San Francisco
val sanFranciscoGeo = MapGeoPoint(37.7749, -122.4194)
mapView.renderer.setMapGeoCenter(sanFranciscoGeo)
mapView.renderer.mapZoom = 12f
// Add a marker
val sanFrancisco = MapPoint(sanFranciscoGeo)
addMarker(sanFrancisco, "pin1.png")
}
private fun addMarker(location: MapPoint, imageName: String) {
val marker = GLMapImage(100) // z-order
// Load image from assets
val inputStream = assets.open(imageName)
val bitmap = android.graphics.BitmapFactory.decodeStream(inputStream)
marker.setBitmap(bitmap)
marker.setPosition(location)
// Center the marker at its position
marker.setOffset(bitmap.width / 2, bitmap.height)
mapView.renderer.add(marker)
}
}
Step 2: Use SVG for Scalable Markers
import globus.glmap.SVGRender
private fun addSVGMarker(location: MapPoint, svgName: String) {
val marker = GLMapImage(100)
// Render SVG to bitmap
val bitmap = SVGRender.render(
assets,
svgName,
SVGRender.transform(mapView.renderer.screenScale)
)
marker.setBitmap(bitmap)
marker.setPosition(location)
marker.setOffset(bitmap.width / 2, bitmap.height)
mapView.renderer.add(marker)
// Recycle bitmap after use
bitmap.recycle()
}
Step 3: Handle Marker Taps
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... previous setup ...
// Add tap listener
mapView.setOnTouchEventCallback { event ->
if (event.action == android.view.MotionEvent.ACTION_UP) {
handleMapTap(event.x, event.y)
}
false // Return false to allow normal map gestures
}
}
private fun handleMapTap(x: Float, y: Float) {
val objects = mapView.renderer.getObjectsAtPoint(x, y, 10f)
for (obj in objects) {
if (obj is GLMapImage) {
showMarkerInfo(obj)
break
}
}
}
private fun showMarkerInfo(marker: GLMapImage) {
android.app.AlertDialog.Builder(this)
.setTitle("Marker Tapped")
.setMessage("Position: ${marker.position}")
.setPositiveButton("OK", null)
.show()
}
Step 4: Add Multiple Markers Efficiently
import globus.glmap.GLMapImageGroup
import globus.glmap.GLMapMarkerData
private fun addMultipleMarkers() {
val locations = listOf(
MapPoint.CreateFromGeoCoordinates(37.8199, -122.4783), // Golden Gate
MapPoint.CreateFromGeoCoordinates(37.8267, -122.4230), // Alcatraz
MapPoint.CreateFromGeoCoordinates(37.8080, -122.4177), // Fisherman's Wharf
MapPoint.CreateFromGeoCoordinates(37.8021, -122.4187) // Lombard Street
)
// Load marker images
val bitmaps = listOf(
loadBitmap("pin1.png"),
loadBitmap("pin2.png"),
loadBitmap("pin3.png")
)
val imageGroup = GLMapImageGroup(100)
imageGroup.setImages(bitmaps.toTypedArray())
locations.forEachIndexed { index, location ->
val markerData = GLMapMarkerData()
markerData.pt = location
markerData.imageIndex = (index % bitmaps.size).toShort()
markerData.offset = MapPoint(
bitmaps[0].width / 2.0,
bitmaps[0].height.toDouble()
)
imageGroup.addMarkerData(markerData)
}
mapView.renderer.add(imageGroup)
// Clean up bitmaps
bitmaps.forEach { it.recycle() }
}
private fun loadBitmap(fileName: String): android.graphics.Bitmap {
val inputStream = assets.open(fileName)
return android.graphics.BitmapFactory.decodeStream(inputStream)
}
Step 5: Marker Clustering (Optional)
import globus.glmap.GLMapMarkerLayer
import globus.glmap.GLMapMarker
private fun setupClusteredMarkers() {
val markerLayer = GLMapMarkerLayer()
// Enable clustering
markerLayer.setClusteringEnabled(true)
markerLayer.setClusteringRadius(30f) // pixels
// Add many markers
repeat(1000) { i ->
val lat = 37.7749 + (-0.1..0.1).random()
val lon = -122.4194 + (-0.1..0.1).random()
val location = MapPoint.CreateFromGeoCoordinates(lat, lon)
val marker = GLMapMarker()
marker.setPoint(location)
marker.setText("Marker $i")
markerLayer.addMarker(marker)
}
mapView.renderer.add(markerLayer)
}
Understanding the Code
Marker Types
GLOBUS provides several ways to display markers:
Type | Best For | Performance |
---|---|---|
GLMapImage | Single markers or small sets (< 10) | Good |
GLMapImageGroup | Multiple markers with few unique images (10-100) | Better |
GLMapMarkerLayer | Large datasets with clustering (100+) | Best |
Z-Order (Layering)
Control which markers appear on top:
// iOS
marker.drawOrder = 100 // Higher numbers draw on top
// Android
val marker = GLMapImage(100) // Pass z-order in constructor
Performance Tips
- Use GLMapImageGroup for multiple markers sharing the same images
- Enable clustering for 100+ markers to prevent screen clutter
- Use SVG for markers that need to scale at different zoom levels
- Recycle bitmaps (Android) after adding to prevent memory leaks
Marker Positioning
The offset
property controls where the image is positioned relative to its geo-coordinate:
// iOS - bottom center of image at coordinate (typical for pins)
marker.offset = CGPoint(x: image.size.width / 2, y: image.size.height)
// For centered markers (like dots)
marker.offset = CGPoint(x: image.size.width / 2, y: image.size.height / 2)
Advanced: Custom Cluster Styling
You can customize how clusters look:
Custom Cluster Appearance
iOS:
markerLayer.clusterImageBlock = { count in
// Create custom cluster image based on marker count
return self.createClusterImage(count: count)
}
func createClusterImage(count: Int) -> UIImage {
let size = CGSize(width: 40, height: 40)
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
// Draw circle
let circle = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size))
UIColor.blue.setFill()
circle.fill()
// Draw count
let text = "\(count)"
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 14, weight: .bold),
.foregroundColor: UIColor.white
]
let textSize = text.size(withAttributes: attributes)
let textRect = CGRect(
x: (size.width - textSize.width) / 2,
y: (size.height - textSize.height) / 2,
width: textSize.width,
height: textSize.height
)
text.draw(in: textRect, withAttributes: attributes)
}
}
Troubleshooting
Markers not appearing:
- Check that coordinates are valid (latitude: -90 to 90, longitude: -180 to 180)
- Verify the map is zoomed to show the marker location
- Ensure images are properly loaded from bundle/assets
Taps not detected:
- Check z-order: higher values needed to be above map
- Verify tap gesture is properly configured
- For Android, ensure
onTouchEventCallback
returns correct value
Performance issues with many markers:
- Use
GLMapImageGroup
instead of individualGLMapImage
objects - Enable clustering for 100+ markers
- Consider only showing markers visible in current viewport
Images look blurry:
- Use SVG instead of PNG for scalable graphics
- Provide @2x and @3x images for iOS
- Use appropriate DPI for Android (mdpi, hdpi, xhdpi, etc.)
What's Next?
Now that you can display markers, explore more advanced features in the demo apps.
Related Resources
- Demo: MarkerLayer – Complete iOS examples
- Demo: Markers – Android marker examples
- MapCSS Reference – Style markers using MapCSS
- API Reference – Full marker API documentation