Skip to main content

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:

TypeBest ForPerformance
GLMapImageSingle markers or small sets (< 10)Good
GLMapImageGroupMultiple markers with few unique images (10-100)Better
GLMapMarkerLayerLarge 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

  1. Use GLMapImageGroup for multiple markers sharing the same images
  2. Enable clustering for 100+ markers to prevent screen clutter
  3. Use SVG for markers that need to scale at different zoom levels
  4. 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 individual GLMapImage 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.